diff options
33 files changed, 620 insertions, 229 deletions
@@ -138,8 +138,8 @@ Dates are provided in the format YYYY-MM-DD. | 1978433 | 2019-11-30 | v12 | v0.15.0.0 | v0.16.0.0 | New PoW based on RandomX, only allow >= 2 outputs, change to the block median used to calculate penalty, v1 coinbases are forbidden, rct sigs in coinbase forbidden, 10 block lock time for incoming outputs | 2210000 | 2020-10-17 | v13 | v0.17.0.0 | v0.17.3.2 | New CLSAG transaction format | 2210720 | 2020-10-18 | v14 | v0.17.1.1 | v0.17.3.2 | forbid old MLSAG transaction format -| 2688888 | 2022-08-13 | v15 | v0.18.0.0 | v0.18.2.2 | ringsize = 16, bulletproofs+, view tags, adjusted dynamic block weight algorithm -| 2689608 | 2022-08-14 | v16 | v0.18.0.0 | v0.18.2.2 | forbid old v14 transaction format +| 2688888 | 2022-08-13 | v15 | v0.18.0.0 | v0.18.3.1 | ringsize = 16, bulletproofs+, view tags, adjusted dynamic block weight algorithm +| 2689608 | 2022-08-14 | v16 | v0.18.0.0 | v0.18.3.1 | forbid old v14 transaction format | XXXXXXX | XXX-XX-XX | XXX | vX.XX.X.X | vX.XX.X.X | XXX | X's indicate that these details have not been determined as of commit date. @@ -344,7 +344,7 @@ Tested on a Raspberry Pi Zero with a clean install of minimal Raspbian Stretch ( ```bash git clone https://github.com/monero-project/monero.git cd monero - git checkout v0.18.2.2 + git checkout v0.18.3.1 ``` * Build: @@ -463,10 +463,10 @@ application. cd monero ``` -* If you would like a specific [version/tag](https://github.com/monero-project/monero/tags), do a git checkout for that version. eg. 'v0.18.2.2'. If you don't care about the version and just want binaries from master, skip this step: +* If you would like a specific [version/tag](https://github.com/monero-project/monero/tags), do a git checkout for that version. eg. 'v0.18.3.1'. If you don't care about the version and just want binaries from master, skip this step: ```bash - git checkout v0.18.2.2 + git checkout v0.18.3.1 ``` * If you are on a 64-bit system, run: diff --git a/contrib/depends/packages/openssl.mk b/contrib/depends/packages/openssl.mk index 100e0d33b..b35cf5e97 100644 --- a/contrib/depends/packages/openssl.mk +++ b/contrib/depends/packages/openssl.mk @@ -1,20 +1,19 @@ package=openssl -$(package)_version=1.1.1u +$(package)_version=3.0.11 $(package)_download_path=https://www.openssl.org/source $(package)_file_name=$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=e2f8d84b523eecd06c7be7626830370300fbcc15386bf5142d72758f6963ebc6 +$(package)_sha256_hash=b3425d3bb4a2218d0697eb41f7fc0cdede016ed19ca49d168b78e8d947887f55 define $(package)_set_vars $(package)_config_env=AR="$($(package)_ar)" ARFLAGS=$($(package)_arflags) RANLIB="$($(package)_ranlib)" CC="$($(package)_cc)" -$(package)_config_env_android=ANDROID_NDK_HOME="$(host_prefix)/native" PATH="$(host_prefix)/native/bin" CC=clang AR=ar RANLIB=ranlib -$(package)_build_env_android=ANDROID_NDK_HOME="$(host_prefix)/native" -$(package)_config_opts=--prefix=$(host_prefix) --openssldir=$(host_prefix)/etc/openssl +$(package)_config_env_android=ANDROID_NDK_ROOT="$(host_prefix)/native" PATH="$(host_prefix)/native/bin" CC=clang AR=ar RANLIB=ranlib +$(package)_build_env_android=ANDROID_NDK_ROOT="$(host_prefix)/native" +$(package)_config_opts=--prefix=$(host_prefix) --openssldir=$(host_prefix)/etc/openssl --libdir=$(host_prefix)/lib $(package)_config_opts+=no-capieng $(package)_config_opts+=no-dso $(package)_config_opts+=no-dtls1 $(package)_config_opts+=no-ec_nistp_64_gcc_128 $(package)_config_opts+=no-gost -$(package)_config_opts+=no-heartbeats $(package)_config_opts+=no-md2 $(package)_config_opts+=no-rc5 $(package)_config_opts+=no-rdrand @@ -22,8 +21,8 @@ $(package)_config_opts+=no-rfc3779 $(package)_config_opts+=no-sctp $(package)_config_opts+=no-shared $(package)_config_opts+=no-ssl-trace -$(package)_config_opts+=no-ssl2 $(package)_config_opts+=no-ssl3 +$(package)_config_opts+=no-tests $(package)_config_opts+=no-unit-test $(package)_config_opts+=no-weak-ssl-ciphers $(package)_config_opts+=no-zlib @@ -49,7 +48,7 @@ $(package)_config_opts_x86_64_freebsd=BSD-x86_64 endef define $(package)_preprocess_cmds - sed -i.old 's|"engines", "apps", "test", "util", "tools", "fuzz"|"engines", "tools"|' Configure + sed -i.old 's|crypto ssl apps util tools fuzz providers doc|crypto ssl util tools providers|' build.info endef define $(package)_config_cmds diff --git a/contrib/depends/packages/unbound.mk b/contrib/depends/packages/unbound.mk index 421c51f7f..166cc3f79 100644 --- a/contrib/depends/packages/unbound.mk +++ b/contrib/depends/packages/unbound.mk @@ -11,6 +11,7 @@ define $(package)_set_vars $(package)_config_opts=--disable-shared --enable-static --without-pyunbound --prefix=$(host_prefix) --with-libexpat=$(host_prefix) --with-ssl=$(host_prefix) --with-libevent=no --without-pythonmodule --disable-flto --with-pthreads --with-libunbound-only $(package)_config_opts_linux=--with-pic $(package)_config_opts_w64=--enable-static-exe --sysconfdir=/etc --prefix=$(host_prefix) --target=$(host_prefix) + $(package)_config_opts_x86_64_darwin=ac_cv_func_SHA384_Init=yes $(package)_build_opts_mingw32=LDFLAGS="$($(package)_ldflags) -lpthread" endef diff --git a/contrib/depends/toolchain.cmake.in b/contrib/depends/toolchain.cmake.in index 570065560..c86012a6a 100644 --- a/contrib/depends/toolchain.cmake.in +++ b/contrib/depends/toolchain.cmake.in @@ -144,8 +144,8 @@ elseif(ARCHITECTURE STREQUAL "aarch64") endif() if(ARCHITECTURE STREQUAL "riscv64") - set(NO_AES ON) - set(ARCH "rv64imafdc") + set(ARCH_ID "riscv64") + set(ARCH "rv64gc") endif() if(ARCHITECTURE STREQUAL "i686") diff --git a/contrib/epee/include/net/http_client.h b/contrib/epee/include/net/http_client.h index 9ce30b620..af1aee9f6 100644 --- a/contrib/epee/include/net/http_client.h +++ b/contrib/epee/include/net/http_client.h @@ -245,8 +245,18 @@ namespace net_utils } } + // This magic var determines the maximum length for when copying the body message in + // memory is faster/more preferable than the round-trip time for one packet + constexpr size_t BODY_NO_COPY_CUTOFF = 128 * 1024; // ~262 KB or ~175 packets + + // Maximum expected total headers bytes + constexpr size_t HEADER_RESERVE_SIZE = 2048; + + const bool do_copy_body = body.size() <= BODY_NO_COPY_CUTOFF; + const size_t req_buff_cap = HEADER_RESERVE_SIZE + (do_copy_body ? body.size() : 0); + std::string req_buff{}; - req_buff.reserve(2048); + req_buff.reserve(req_buff_cap); req_buff.append(method.data(), method.size()).append(" ").append(uri.data(), uri.size()).append(" HTTP/1.1\r\n"); add_field(req_buff, "Host", m_host_buff); add_field(req_buff, "Content-Length", std::to_string(body.size())); @@ -255,9 +265,7 @@ namespace net_utils for(const auto& field : additional_params) add_field(req_buff, field); - for (unsigned sends = 0; sends < 2; ++sends) { - const std::size_t initial_size = req_buff.size(); const auto auth = m_auth.get_auth_field(method, uri); if (auth) add_field(req_buff, *auth); @@ -265,11 +273,21 @@ namespace net_utils req_buff += "\r\n"; //-- - bool res = m_net_client.send(req_buff, timeout); - CHECK_AND_ASSERT_MES(res, false, "HTTP_CLIENT: Failed to SEND"); - if(body.size()) + if (do_copy_body) // small body + { + // Copy headers + body together and potentially send one fewer packet + req_buff.append(body.data(), body.size()); + const bool res = m_net_client.send(req_buff, timeout); + CHECK_AND_ASSERT_MES(res, false, "HTTP_CLIENT: Failed to SEND"); + } + else // large body + { + // Send headers and body seperately to avoid copying heavy body message + bool res = m_net_client.send(req_buff, timeout); + CHECK_AND_ASSERT_MES(res, false, "HTTP_CLIENT: Failed to SEND"); res = m_net_client.send(body, timeout); - CHECK_AND_ASSERT_MES(res, false, "HTTP_CLIENT: Failed to SEND"); + CHECK_AND_ASSERT_MES(res, false, "HTTP_CLIENT: Failed to SEND"); + } m_response_info.clear(); m_state = reciev_machine_state_header; @@ -282,19 +300,11 @@ namespace net_utils return true; } - switch (m_auth.handle_401(m_response_info)) + if (m_auth.handle_401(m_response_info) == http_client_auth::kParseFailure) { - case http_client_auth::kSuccess: - break; - case http_client_auth::kBadPassword: - sends = 2; - break; - default: - case http_client_auth::kParseFailure: LOG_ERROR("Bad server response for authentication"); return false; } - req_buff.resize(initial_size); // rollback for new auth generation } LOG_ERROR("Client has incorrect username/password for server requiring authentication"); return false; diff --git a/contrib/epee/include/storages/portable_storage_from_bin.h b/contrib/epee/include/storages/portable_storage_from_bin.h index d8a8a4a49..b0af022f5 100644 --- a/contrib/epee/include/storages/portable_storage_from_bin.h +++ b/contrib/epee/include/storages/portable_storage_from_bin.h @@ -33,6 +33,9 @@ #include "portable_storage_base.h" #include "portable_storage_bin_utils.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "serialization" + #ifdef EPEE_PORTABLE_STORAGE_RECURSION_LIMIT #define EPEE_PORTABLE_STORAGE_RECURSION_LIMIT_INTERNAL EPEE_PORTABLE_STORAGE_RECURSION_LIMIT #else diff --git a/contrib/epee/include/storages/portable_storage_from_json.h b/contrib/epee/include/storages/portable_storage_from_json.h index 69192ca6b..f21a89a92 100644 --- a/contrib/epee/include/storages/portable_storage_from_json.h +++ b/contrib/epee/include/storages/portable_storage_from_json.h @@ -31,6 +31,9 @@ #include "parserse_base_utils.h" #include "file_io_utils.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "serialization" + #define EPEE_JSON_RECURSION_LIMIT_INTERNAL 100 namespace epee diff --git a/contrib/epee/src/mlog.cpp b/contrib/epee/src/mlog.cpp index 092d41777..4ca1a3632 100644 --- a/contrib/epee/src/mlog.cpp +++ b/contrib/epee/src/mlog.cpp @@ -338,11 +338,21 @@ bool is_stdout_a_tty() return is_a_tty.load(std::memory_order_relaxed); } +static bool is_nocolor() +{ + static const char *no_color_var = getenv("NO_COLOR"); + static const bool no_color = no_color_var && *no_color_var; // apparently, NO_COLOR=0 means no color too (as per no-color.org) + return no_color; +} + void set_console_color(int color, bool bright) { if (!is_stdout_a_tty()) return; + if (is_nocolor()) + return; + switch(color) { case console_color_default: @@ -461,6 +471,9 @@ void reset_console_color() { if (!is_stdout_a_tty()) return; + if (is_nocolor()) + return; + #ifdef WIN32 HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute(h_stdout, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); diff --git a/contrib/epee/src/readline_buffer.cpp b/contrib/epee/src/readline_buffer.cpp index 1047d1696..ac68d1fdb 100644 --- a/contrib/epee/src/readline_buffer.cpp +++ b/contrib/epee/src/readline_buffer.cpp @@ -238,6 +238,10 @@ static char** attempted_completion(const char* text, int start, int end) static void install_line_handler() { +#if RL_READLINE_VERSION >= 0x0801 + rl_variable_bind("enable-bracketed-paste", "off"); +#endif + rl_attempted_completion_function = attempted_completion; rl_callback_handler_install("", handle_line); stifle_history(500); diff --git a/contrib/gitian/DOCKRUN.md b/contrib/gitian/DOCKRUN.md index d8cf8222e..5e5092507 100644 --- a/contrib/gitian/DOCKRUN.md +++ b/contrib/gitian/DOCKRUN.md @@ -57,7 +57,7 @@ The dockrun.sh script will do everything to build the binaries. Just specify the version to build as its only argument, e.g. ```bash -VERSION=v0.18.2.2 +VERSION=v0.18.3.1 ./dockrun.sh $VERSION ``` diff --git a/contrib/gitian/README.md b/contrib/gitian/README.md index 153af7541..d5f6ba0e0 100644 --- a/contrib/gitian/README.md +++ b/contrib/gitian/README.md @@ -133,7 +133,7 @@ Common setup part: su - gitianuser GH_USER=YOUR_GITHUB_USER_NAME -VERSION=v0.18.2.2 +VERSION=v0.18.3.1 ``` Where `GH_USER` is your GitHub user name and `VERSION` is the version tag you want to build. diff --git a/contrib/gitian/gitian-linux.yml b/contrib/gitian/gitian-linux.yml index 63d2bc5d2..41915deb9 100644 --- a/contrib/gitian/gitian-linux.yml +++ b/contrib/gitian/gitian-linux.yml @@ -21,6 +21,7 @@ packages: - "g++-7-arm-linux-gnueabihf" - "gcc-arm-linux-gnueabihf" - "g++-arm-linux-gnueabihf" +- "g++-riscv64-linux-gnu" - "g++-7-multilib" - "gcc-7-multilib" - "binutils-arm-linux-gnueabihf" @@ -43,7 +44,7 @@ files: [] script: | WRAP_DIR=$HOME/wrapped - HOSTS="x86_64-linux-gnu arm-linux-gnueabihf aarch64-linux-gnu i686-linux-gnu" + HOSTS="x86_64-linux-gnu arm-linux-gnueabihf aarch64-linux-gnu i686-linux-gnu riscv64-linux-gnu" FAKETIME_HOST_PROGS="" FAKETIME_PROGS="date" HOST_CFLAGS="-O2 -g" @@ -159,7 +160,13 @@ script: | fi export C_INCLUDE_PATH="$EXTRA_INCLUDES" export CPLUS_INCLUDE_PATH="$EXTRA_INCLUDES" - cmake .. -DCMAKE_TOOLCHAIN_FILE=${BASEPREFIX}/${i}/share/toolchain.cmake -DBACKCOMPAT=ON -DCMAKE_SKIP_RPATH=ON + # glibc only added riscv support in 2.27, disable backwards compatibility + if [ "$i" == "riscv64-linux-gnu" ]; then + BACKCOMPAT_OPTION=OFF + else + BACKCOMPAT_OPTION=ON + fi + cmake .. -DCMAKE_TOOLCHAIN_FILE=${BASEPREFIX}/${i}/share/toolchain.cmake -DBACKCOMPAT=${BACKCOMPAT_OPTION} -DCMAKE_SKIP_RPATH=ON make ${MAKEOPTS} chmod 755 bin/* cp ../LICENSE ../README.md ../docs/ANONYMITY_NETWORKS.md bin diff --git a/external/easylogging++/easylogging++.cc b/external/easylogging++/easylogging++.cc index a765ee8cc..891936b6b 100644 --- a/external/easylogging++/easylogging++.cc +++ b/external/easylogging++/easylogging++.cc @@ -149,6 +149,11 @@ static el::Color colorFromLevel(el::Level level) static void setConsoleColor(el::Color color, bool bright) { + static const char *no_color_var = getenv("NO_COLOR"); + static const bool no_color = no_color_var && *no_color_var; // apparently, NO_COLOR=0 means no color too (as per no-color.org) + if (no_color) + return; + #if ELPP_OS_WINDOWS HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); switch (color) diff --git a/external/randomx b/external/randomx -Subproject 261d58c77fc5547c0aa7fdfeb58421ba7e0e6e1 +Subproject 102f8acf90a7649ada410de5499a7ec62e49e1d diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat Binary files differindex eb3a40fa2..bb6f9b421 100644 --- a/src/blocks/checkpoints.dat +++ b/src/blocks/checkpoints.dat diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index 507e807e2..fe1508e21 100644 --- a/src/checkpoints/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -246,6 +246,8 @@ namespace cryptonote ADD_CHECKPOINT2(2817000, "39726d19ccaac01d150bec827b877ffae710b516bd633503662036ef4422e577", "0x3900669561954c1"); ADD_CHECKPOINT2(2844000, "28fc7b446dfef5b469f5778eb72ddf32a307a5f5a9823d1c394e772349e05d40", "0x3af384ec0e97d12"); ADD_CHECKPOINT2(2851000, "5bf0e47fc782263191a33f63a67db6c711781dc2a3c442e17ed901ec401be5c9", "0x3b6cd8a8ed610e8"); + ADD_CHECKPOINT2(2971000, "3d4cac5ac515eeabd18769ab943af85f36db51d28720def0d0e6effc2c8f5ce3", "0x436e532738b8b5b"); + ADD_CHECKPOINT2(2985000, "08f5e6b7301c1b6ed88268a28f8677a06e8ff943b3f9e48d3080f71f9c134bfb", "0x444b7b42a633c96"); return true; } diff --git a/src/crypto/jh.c b/src/crypto/jh.c index 12d536375..738c681f8 100644 --- a/src/crypto/jh.c +++ b/src/crypto/jh.c @@ -34,7 +34,7 @@ typedef struct { unsigned long long databitlen; /*the message size in bits*/ unsigned long long datasize_in_buffer; /*the size of the message remained in buffer; assumed to be multiple of 8bits except for the last partial block at the end of the message*/ DATA_ALIGN16(uint64 x[8][2]); /*the 1024-bit state, ( x[i][0] || x[i][1] ) is the ith row of the state in the pseudocode*/ - unsigned char buffer[64]; /*the 512-bit message block to be hashed;*/ + DATA_ALIGN16(unsigned char buffer[64]); /*the 512-bit message block to be hashed;*/ } hashState; @@ -213,16 +213,24 @@ static void E8(hashState *state) /*The compression function F8 */ static void F8(hashState *state) { - uint64 i; + uint64_t* x = (uint64_t*)state->x; /*xor the 512-bit message with the fist half of the 1024-bit hash state*/ - for (i = 0; i < 8; i++) state->x[i >> 1][i & 1] ^= ((uint64*)state->buffer)[i]; + for (int i = 0; i < 8; ++i) { + uint64 b; + memcpy(&b, &state->buffer[i << 3], sizeof(b)); + x[i] ^= b; + } /*the bijective function E8 */ E8(state); /*xor the 512-bit message with the second half of the 1024-bit hash state*/ - for (i = 0; i < 8; i++) state->x[(8+i) >> 1][(8+i) & 1] ^= ((uint64*)state->buffer)[i]; + for (int i = 0; i < 8; ++i) { + uint64 b; + memcpy(&b, &state->buffer[i << 3], sizeof(b)); + x[i + 8] ^= b; + } } /*before hashing a message, initialize the hash state as H0 */ @@ -240,6 +248,7 @@ static HashReturn Init(hashState *state, int hashbitlen) case 224: memcpy(state->x,JH224_H0,128); break; case 256: memcpy(state->x,JH256_H0,128); break; case 384: memcpy(state->x,JH384_H0,128); break; + default: case 512: memcpy(state->x,JH512_H0,128); break; } diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index bac49aa94..61146a114 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -30,6 +30,7 @@ #pragma once +#include <cstdint> #include <stdexcept> #include <string> #include <boost/uuid/uuid.hpp> diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 2620010c4..9d8f0f4de 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -4616,40 +4616,9 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti } else { - const uint64_t block_weight = m_db->get_block_weight(db_height - 1); + const uint64_t nblocks = std::min<uint64_t>(m_long_term_block_weights_window, db_height); + const uint64_t long_term_median = get_long_term_block_weight_median(db_height - nblocks, nblocks); - uint64_t long_term_median; - if (db_height == 1) - { - long_term_median = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5; - } - else - { - uint64_t nblocks = std::min<uint64_t>(m_long_term_block_weights_window, db_height); - if (nblocks == db_height) - --nblocks; - long_term_median = get_long_term_block_weight_median(db_height - nblocks - 1, nblocks); - } - - m_long_term_effective_median_block_weight = std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); - - uint64_t short_term_constraint = m_long_term_effective_median_block_weight; - if (hf_version >= HF_VERSION_2021_SCALING) - short_term_constraint += m_long_term_effective_median_block_weight * 7 / 10; - else - short_term_constraint += m_long_term_effective_median_block_weight * 2 / 5; - uint64_t long_term_block_weight = std::min<uint64_t>(block_weight, short_term_constraint); - - if (db_height == 1) - { - long_term_median = long_term_block_weight; - } - else - { - m_long_term_block_weights_cache_tip_hash = m_db->get_block_hash_from_height(db_height - 1); - m_long_term_block_weights_cache_rolling_median.insert(long_term_block_weight); - long_term_median = m_long_term_block_weights_cache_rolling_median.median(); - } m_long_term_effective_median_block_weight = std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); std::vector<uint64_t> weights; @@ -5582,7 +5551,7 @@ void Blockchain::cancel() } #if defined(PER_BLOCK_CHECKPOINT) -static const char expected_block_hashes_hash[] = "2c95b5af1f3ee41893ae0c585fd59207a40f28ed4addbaad64a46a39b82955e7"; +static const char expected_block_hashes_hash[] = "bc9c91329af96137390d9c709fa3cecc924f1b25dadb7589f0d751cd93f3cc39"; void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get_checkpoints) { if (get_checkpoints == nullptr || !m_fast_sync) diff --git a/src/multisig/multisig_kex_msg.cpp b/src/multisig/multisig_kex_msg.cpp index c717e23ad..024d71df7 100644 --- a/src/multisig/multisig_kex_msg.cpp +++ b/src/multisig/multisig_kex_msg.cpp @@ -206,8 +206,13 @@ namespace multisig //---------------------------------------------------------------------------------------------------------------------- void multisig_kex_msg::parse_and_validate_msg() { + CHECK_AND_ASSERT_THROW_MES(MULTISIG_KEX_MSG_V2_MAGIC_1.size() == MULTISIG_KEX_MSG_V2_MAGIC_N.size(), + "Multisig kex msg magic inconsistency."); + CHECK_AND_ASSERT_THROW_MES(MULTISIG_KEX_MSG_V2_MAGIC_1.size() >= MULTISIG_KEX_V1_MAGIC.size(), + "Multisig kex msg magic inconsistency."); + // check message type - CHECK_AND_ASSERT_THROW_MES(m_msg.size() > 0, "Kex message unexpectedly empty."); + CHECK_AND_ASSERT_THROW_MES(m_msg.size() >= MULTISIG_KEX_MSG_V2_MAGIC_1.size(), "Kex message unexpectedly small."); CHECK_AND_ASSERT_THROW_MES(m_msg.substr(0, MULTISIG_KEX_V1_MAGIC.size()) != MULTISIG_KEX_V1_MAGIC, "V1 multisig kex messages are deprecated (unsafe)."); CHECK_AND_ASSERT_THROW_MES(m_msg.substr(0, MULTISIG_KEX_MSG_V1_MAGIC.size()) != MULTISIG_KEX_MSG_V1_MAGIC, @@ -215,8 +220,6 @@ namespace multisig // deserialize the message std::string msg_no_magic; - CHECK_AND_ASSERT_THROW_MES(MULTISIG_KEX_MSG_V2_MAGIC_1.size() == MULTISIG_KEX_MSG_V2_MAGIC_N.size(), - "Multisig kex msg magic inconsistency."); CHECK_AND_ASSERT_THROW_MES(tools::base58::decode(m_msg.substr(MULTISIG_KEX_MSG_V2_MAGIC_1.size()), msg_no_magic), "Multisig kex msg decoding error."); binary_archive<false> b_archive{epee::strspan<std::uint8_t>(msg_no_magic)}; diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index ab1a26b26..32cd8dc6f 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -362,11 +362,17 @@ namespace rct { { if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus) { + // Since RCTTypeBulletproof2 enote types, we don't serialize the blinding factor, and only serialize the + // first 8 bytes of ecdhInfo[i].amount ar.begin_object(); - if (!typename Archive<W>::is_saving()) + crypto::hash8 trunc_amount; // placeholder variable needed to maintain "strict aliasing" + if (!typename Archive<W>::is_saving()) // loading memset(ecdhInfo[i].amount.bytes, 0, sizeof(ecdhInfo[i].amount.bytes)); - crypto::hash8 &amount = (crypto::hash8&)ecdhInfo[i].amount; - FIELD(amount); + else // saving + memcpy(trunc_amount.data, ecdhInfo[i].amount.bytes, sizeof(trunc_amount)); + FIELD(trunc_amount); + if (!typename Archive<W>::is_saving()) // loading + memcpy(ecdhInfo[i].amount.bytes, trunc_amount.data, sizeof(trunc_amount)); ar.end_object(); } else diff --git a/src/rpc/zmq_server.cpp b/src/rpc/zmq_server.cpp index 398a0499a..7ebb6c49f 100644 --- a/src/rpc/zmq_server.cpp +++ b/src/rpc/zmq_server.cpp @@ -158,13 +158,22 @@ void ZmqServer::serve() if (!pub || sockets[2].revents) { - std::string message = MONERO_UNWRAP(net::zmq::receive(rep.get(), read_flags)); - MDEBUG("Received RPC request: \"" << message << "\""); - epee::byte_slice response = handler.handle(std::move(message)); - - const boost::string_ref response_view{reinterpret_cast<const char*>(response.data()), response.size()}; - MDEBUG("Sending RPC reply: \"" << response_view << "\""); - MONERO_UNWRAP(net::zmq::send(std::move(response), rep.get())); + expect<std::string> message = net::zmq::receive(rep.get(), read_flags); + if (!message) + { + // EAGAIN can occur when using `zmq_poll`, which doesn't inspect for message validity + if (message != net::zmq::make_error_code(EAGAIN)) + MONERO_THROW(message.error(), "Read failure on ZMQ-RPC"); + } + else // no errors + { + MDEBUG("Received RPC request: \"" << *message << "\""); + epee::byte_slice response = handler.handle(std::move(*message)); + + const boost::string_ref response_view{reinterpret_cast<const char*>(response.data()), response.size()}; + MDEBUG("Sending RPC reply: \"" << response_view << "\""); + MONERO_UNWRAP(net::zmq::send(std::move(response), rep.get())); + } } } } diff --git a/src/version.cpp.in b/src/version.cpp.in index 76cae29eb..c93430003 100644 --- a/src/version.cpp.in +++ b/src/version.cpp.in @@ -1,5 +1,5 @@ #define DEF_MONERO_VERSION_TAG "@VERSIONTAG@" -#define DEF_MONERO_VERSION "0.18.2.2" +#define DEF_MONERO_VERSION "0.18.3.1" #define DEF_MONERO_RELEASE_NAME "Fluorine Fermi" #define DEF_MONERO_VERSION_FULL DEF_MONERO_VERSION "-" DEF_MONERO_VERSION_TAG #define DEF_MONERO_VERSION_IS_RELEASE @VERSION_IS_RELEASE@ diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 42e2f1d41..0218c8292 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1003,6 +1003,24 @@ uint64_t num_priv_multisig_keys_post_setup(uint64_t threshold, uint64_t total) return n_multisig_keys; } +/** + * @brief Derives the chacha key to encrypt wallet cache files given the chacha key to encrypt the wallet keys files + * + * @param keys_data_key the chacha key that encrypts wallet keys files + * @return crypto::chacha_key the chacha key that encrypts the wallet cache files + */ +crypto::chacha_key derive_cache_key(const crypto::chacha_key& keys_data_key) +{ + static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key"); + + crypto::chacha_key cache_key; + epee::mlocked<tools::scrubbed_arr<char, HASH_SIZE+1>> cache_key_data; + memcpy(cache_key_data.data(), &keys_data_key, HASH_SIZE); + cache_key_data[HASH_SIZE] = config::HASH_KEY_WALLET_CACHE; + cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&) cache_key); + + return cache_key; +} //----------------------------------------------------------------- } //namespace @@ -3901,7 +3919,7 @@ std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> wallet2::create return cache; } //---------------------------------------------------------------------------------------------------- -void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool, bool try_incremental) +void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool, bool try_incremental, uint64_t max_blocks) { if (m_offline) { @@ -3996,7 +4014,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo // infer when we get an incoming output bool first = true, last = false; - while(m_run.load(std::memory_order_relaxed)) + while(m_run.load(std::memory_order_relaxed) && blocks_fetched < max_blocks) { uint64_t next_blocks_start_height; std::vector<cryptonote::block_complete_entry> next_blocks; @@ -4406,6 +4424,10 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: crypto::chacha_key key; crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); + // We use m_cache_key as a deterministic test to see if given key corresponds to original password + const crypto::chacha_key cache_key = derive_cache_key(key); + THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password); + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) { account.encrypt_viewkey(key); @@ -4438,7 +4460,7 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: value2.SetInt(m_key_device_type); json.AddMember("key_on_device", value2, json.GetAllocator()); - value2.SetInt(watch_only ? 1 :0); // WTF ? JSON has different true and false types, and not boolean ?? + value2.SetInt((watch_only || m_watch_only) ? 1 :0); // WTF ? JSON has different true and false types, and not boolean ?? json.AddMember("watch_only", value2, json.GetAllocator()); value2.SetInt(m_multisig ? 1 :0); @@ -4630,11 +4652,8 @@ void wallet2::setup_keys(const epee::wipeable_string &password) m_account.decrypt_viewkey(key); } - static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key"); - epee::mlocked<tools::scrubbed_arr<char, HASH_SIZE+1>> cache_key_data; - memcpy(cache_key_data.data(), &key, HASH_SIZE); - cache_key_data[HASH_SIZE] = config::HASH_KEY_WALLET_CACHE; - cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&)m_cache_key); + m_cache_key = derive_cache_key(key); + get_ringdb_key(); } //---------------------------------------------------------------------------------------------------- @@ -4643,9 +4662,8 @@ void wallet2::change_password(const std::string &filename, const epee::wipeable_ if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) decrypt_keys(original_password); setup_keys(new_password); - rewrite(filename, new_password); if (!filename.empty()) - store(); + store_to(filename, new_password, true); // force rewrite keys file to possible new location } //---------------------------------------------------------------------------------------------------- /*! @@ -5151,6 +5169,10 @@ void wallet2::encrypt_keys(const crypto::chacha_key &key) void wallet2::decrypt_keys(const crypto::chacha_key &key) { + // We use m_cache_key as a deterministic test to see if given key corresponds to original password + const crypto::chacha_key cache_key = derive_cache_key(key); + THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password); + m_account.encrypt_viewkey(key); m_account.decrypt_keys(key); } @@ -6222,6 +6244,20 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass if (!m_persistent_rpc_client_id) set_rpc_client_secret_key(rct::rct2sk(rct::skGen())); + // Wallets used to wipe, but not erase, old unused multisig key info, which lead to huge memory leaks. + // Here we erase these multisig keys if they're zero'd out to free up space. + for (auto &td : m_transfers) + { + auto mk_it = td.m_multisig_k.begin(); + while (mk_it != td.m_multisig_k.end()) + { + if (*mk_it == rct::zero()) + mk_it = td.m_multisig_k.erase(mk_it); + else + ++mk_it; + } + } + cryptonote::block genesis; generate_genesis(genesis); crypto::hash genesis_hash = get_block_hash(genesis); @@ -6311,22 +6347,32 @@ void wallet2::store() store_to("", epee::wipeable_string()); } //---------------------------------------------------------------------------------------------------- -void wallet2::store_to(const std::string &path, const epee::wipeable_string &password) +void wallet2::store_to(const std::string &path, const epee::wipeable_string &password, bool force_rewrite_keys) { trim_hashchain(); + const bool had_old_wallet_files = !m_wallet_file.empty(); + THROW_WALLET_EXCEPTION_IF(!had_old_wallet_files && path.empty(), error::wallet_internal_error, + "Cannot resave wallet to current file since wallet was not loaded from file to begin with"); + // if file is the same, we do: - // 1. save wallet to the *.new file - // 2. remove old wallet file - // 3. rename *.new to wallet_name + // 1. overwrite the keys file iff force_rewrite_keys is specified + // 2. save cache to the *.new file + // 3. rename *.new to wallet_name, replacing old cache file + // else we do: + // 1. prepare new file names with "path" variable + // 2. store new keys files + // 3. remove old keys file + // 4. store new cache file + // 5. remove old cache file // handle if we want just store wallet state to current files (ex store() replacement); - bool same_file = true; - if (!path.empty()) + bool same_file = had_old_wallet_files && path.empty(); + if (had_old_wallet_files && !path.empty()) { - std::string canonical_path = boost::filesystem::canonical(m_wallet_file).string(); - size_t pos = canonical_path.find(path); - same_file = pos != std::string::npos; + const std::string canonical_old_path = boost::filesystem::canonical(m_wallet_file).string(); + const std::string canonical_new_path = boost::filesystem::weakly_canonical(path).string(); + same_file = canonical_old_path == canonical_new_path; } @@ -6347,7 +6393,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas } // get wallet cache data - boost::optional<wallet2::cache_file_data> cache_file_data = get_cache_file_data(password); + boost::optional<wallet2::cache_file_data> cache_file_data = get_cache_file_data(); THROW_WALLET_EXCEPTION_IF(cache_file_data == boost::none, error::wallet_internal_error, "failed to generate wallet cache data"); const std::string new_file = same_file ? m_wallet_file + ".new" : path; @@ -6356,12 +6402,20 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas const std::string old_address_file = m_wallet_file + ".address.txt"; const std::string old_mms_file = m_mms_file; - // save keys to the new file - // if we here, main wallet file is saved and we only need to save keys and address files - if (!same_file) { + if (!same_file) + { prepare_file_names(path); - bool r = store_keys(m_keys_file, password, false); + } + + if (!same_file || force_rewrite_keys) + { + bool r = store_keys(m_keys_file, password, m_watch_only); THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + } + + if (!same_file && had_old_wallet_files) + { + bool r = false; if (boost::filesystem::exists(old_address_file)) { // save address to the new file @@ -6374,11 +6428,6 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas LOG_ERROR("error removing file: " << old_address_file); } } - // remove old wallet file - r = boost::filesystem::remove(old_file); - if (!r) { - LOG_ERROR("error removing file: " << old_file); - } // remove old keys file r = boost::filesystem::remove(old_keys_file); if (!r) { @@ -6392,8 +6441,9 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas LOG_ERROR("error removing file: " << old_mms_file); } } - } else { - // save to new file + } + + // Save cache to new file. If storing to the same file, the temp path has the ".new" extension #ifdef WIN32 // On Windows avoid using std::ofstream which does not work with UTF-8 filenames // The price to pay is temporary higher memory consumption for string stream + binary archive @@ -6413,10 +6463,20 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas THROW_WALLET_EXCEPTION_IF(!success || !ostr.good(), error::file_save_error, new_file); #endif + if (same_file) + { // here we have "*.new" file, we need to rename it to be without ".new" std::error_code e = tools::replace_file(new_file, m_wallet_file); THROW_WALLET_EXCEPTION_IF(e, error::file_save_error, m_wallet_file, e); } + else if (!same_file && had_old_wallet_files) + { + // remove old wallet file + bool r = boost::filesystem::remove(old_file); + if (!r) { + LOG_ERROR("error removing file: " << old_file); + } + } if (m_message_store.get_active()) { @@ -6426,7 +6486,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas } } //---------------------------------------------------------------------------------------------------- -boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data(const epee::wipeable_string &passwords) +boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data() { trim_hashchain(); try @@ -7077,7 +7137,10 @@ void wallet2::commit_tx(pending_tx& ptx) // tx generated, get rid of used k values for (size_t idx: ptx.selected_transfers) + { memwipe(m_transfers[idx].m_multisig_k.data(), m_transfers[idx].m_multisig_k.size() * sizeof(m_transfers[idx].m_multisig_k[0])); + m_transfers[idx].m_multisig_k.clear(); + } //fee includes dust if dust policy specified it. LOG_PRINT_L1("Transaction successfully sent. <" << txid << ">" << ENDL @@ -7581,7 +7644,10 @@ std::string wallet2::save_multisig_tx(multisig_tx_set txs) // txes generated, get rid of used k values for (size_t n = 0; n < txs.m_ptx.size(); ++n) for (size_t idx: txs.m_ptx[n].construction_data.selected_transfers) + { memwipe(m_transfers[idx].m_multisig_k.data(), m_transfers[idx].m_multisig_k.size() * sizeof(m_transfers[idx].m_multisig_k[0])); + m_transfers[idx].m_multisig_k.clear(); + } // zero out some data we don't want to share for (auto &ptx: txs.m_ptx) @@ -7905,7 +7971,10 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto // inputs in the transactions worked on here) for (size_t n = 0; n < exported_txs.m_ptx.size(); ++n) for (size_t idx: exported_txs.m_ptx[n].construction_data.selected_transfers) + { memwipe(m_transfers[idx].m_multisig_k.data(), m_transfers[idx].m_multisig_k.size() * sizeof(m_transfers[idx].m_multisig_k[0])); + m_transfers[idx].m_multisig_k.clear(); + } exported_txs.m_signers.insert(get_multisig_signer_public_key()); @@ -10928,7 +10997,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp else { LOG_PRINT_L2("We made a tx, adjusting fee and saving it, we need " << print_money(needed_fee) << " and we have " << print_money(test_ptx.fee)); - while (needed_fee > test_ptx.fee) { + do { if (use_rct) transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, test_tx, test_ptx, rct_config, use_view_tags); @@ -10939,7 +11008,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask); LOG_PRINT_L2("Made an attempt at a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); - } + } while (needed_fee > test_ptx.fee); LOG_PRINT_L2("Made a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); @@ -11335,7 +11404,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton THROW_WALLET_EXCEPTION_IF(needed_fee > available_for_fee, error::wallet_internal_error, "Transaction cannot pay for itself"); do { - LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); + LOG_PRINT_L2("We made a tx, adjusting fee and saving it, we need " << print_money(needed_fee) << " and we have " << print_money(test_ptx.fee)); // distribute total transferred amount between outputs uint64_t amount_transferred = available_for_fee - needed_fee; uint64_t dt_amount = amount_transferred / outputs; @@ -14220,7 +14289,10 @@ cryptonote::blobdata wallet2::export_multisig() transfer_details &td = m_transfers[n]; crypto::key_image ki; if (td.m_multisig_k.size()) + { memwipe(td.m_multisig_k.data(), td.m_multisig_k.size() * sizeof(td.m_multisig_k[0])); + td.m_multisig_k.clear(); + } info[n].m_LR.clear(); info[n].m_partial_key_images.clear(); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index baeffe096..877e5afd0 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -940,22 +940,32 @@ private: /*! * \brief store_to Stores wallet to another file(s), deleting old ones * \param path Path to the wallet file (keys and address filenames will be generated based on this filename) - * \param password Password to protect new wallet (TODO: probably better save the password in the wallet object?) + * \param password Password that currently locks the wallet + * \param force_rewrite_keys if true, always rewrite keys file + * + * Leave both "path" and "password" blank to restore the cache file to the current position in the disk + * (which is the same as calling `store()`). If you want to store the wallet with a new password, + * use the method `change_password()`. + * + * Normally the keys file is not overwritten when storing, except when force_rewrite_keys is true + * or when `path` is a new wallet file. + * + * \throw error::invalid_password If storing keys file and old password is incorrect */ - void store_to(const std::string &path, const epee::wipeable_string &password); + void store_to(const std::string &path, const epee::wipeable_string &password, bool force_rewrite_keys = false); /*! * \brief get_keys_file_data Get wallet keys data which can be stored to a wallet file. - * \param password Password of the encrypted wallet buffer (TODO: probably better save the password in the wallet object?) + * \param password Password that currently locks the wallet * \param watch_only true to include only view key, false to include both spend and view keys * \return Encrypted wallet keys data which can be stored to a wallet file + * \throw error::invalid_password if password does not match current wallet */ boost::optional<wallet2::keys_file_data> get_keys_file_data(const epee::wipeable_string& password, bool watch_only); /*! * \brief get_cache_file_data Get wallet cache data which can be stored to a wallet file. - * \param password Password to protect the wallet cache data (TODO: probably better save the password in the wallet object?) - * \return Encrypted wallet cache data which can be stored to a wallet file + * \return Encrypted wallet cache data which can be stored to a wallet file (using current password) */ - boost::optional<wallet2::cache_file_data> get_cache_file_data(const epee::wipeable_string& password); + boost::optional<wallet2::cache_file_data> get_cache_file_data(); std::string path() const; @@ -1048,7 +1058,7 @@ private: bool is_deprecated() const; void refresh(bool trusted_daemon); void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched); - void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool = true, bool try_incremental = true); + void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool = true, bool try_incremental = true, uint64_t max_blocks = std::numeric_limits<uint64_t>::max()); bool refresh(bool trusted_daemon, uint64_t & blocks_fetched, bool& received_money, bool& ok); void set_refresh_type(RefreshType refresh_type) { m_refresh_type = refresh_type; } diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 8119d9d09..cecd79368 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -60,6 +60,7 @@ using namespace epee; #define MONERO_DEFAULT_LOG_CATEGORY "wallet.rpc" #define DEFAULT_AUTO_REFRESH_PERIOD 20 // seconds +#define REFRESH_INFICATIVE_BLOCK_CHUNK_SIZE 256 // just to split refresh in separate calls to play nicer with other threads #define CHECK_MULTISIG_ENABLED() \ do \ @@ -79,6 +80,7 @@ namespace const command_line::arg_descriptor<bool> arg_restricted = {"restricted-rpc", "Restricts to view-only commands", false}; const command_line::arg_descriptor<std::string> arg_wallet_dir = {"wallet-dir", "Directory for newly created wallets"}; const command_line::arg_descriptor<bool> arg_prompt_for_password = {"prompt-for-password", "Prompts for password when not provided", false}; + const command_line::arg_descriptor<bool> arg_no_initial_sync = {"no-initial-sync", "Skips the initial sync before listening for connections", false}; constexpr const char default_rpc_username[] = "monero"; @@ -152,11 +154,14 @@ namespace tools uint64_t blocks_fetched = 0; try { bool received_money = false; - if (m_wallet) m_wallet->refresh(m_wallet->is_trusted_daemon(), 0, blocks_fetched, received_money, true, true); + if (m_wallet) m_wallet->refresh(m_wallet->is_trusted_daemon(), 0, blocks_fetched, received_money, true, true, REFRESH_INFICATIVE_BLOCK_CHUNK_SIZE); } catch (const std::exception& ex) { LOG_ERROR("Exception at while refreshing, what=" << ex.what()); } - m_last_auto_refresh_time = boost::posix_time::microsec_clock::universal_time(); + // if we got the max amount of blocks, do not set the last refresh time, we did only part of the refresh and will + // continue asap, and only set the last refresh time once the refresh is actually finished + if (blocks_fetched < REFRESH_INFICATIVE_BLOCK_CHUNK_SIZE) + m_last_auto_refresh_time = boost::posix_time::microsec_clock::universal_time(); return true; }, 1000); m_net_server.add_idle_handler([this](){ @@ -4554,6 +4559,7 @@ public: const auto password_file = command_line::get_arg(vm, arg_password_file); const auto prompt_for_password = command_line::get_arg(vm, arg_prompt_for_password); const auto password_prompt = prompt_for_password ? password_prompter : nullptr; + const auto no_initial_sync = command_line::get_arg(vm, arg_no_initial_sync); if(!wallet_file.empty() && !from_json.empty()) { @@ -4622,7 +4628,8 @@ public: try { - wal->refresh(wal->is_trusted_daemon()); + if (!no_initial_sync) + wal->refresh(wal->is_trusted_daemon()); } catch (const std::exception& e) { @@ -4733,6 +4740,7 @@ int main(int argc, char** argv) { command_line::add_arg(desc_params, arg_wallet_dir); command_line::add_arg(desc_params, arg_prompt_for_password); command_line::add_arg(desc_params, arg_rpc_client_secret_key); + command_line::add_arg(desc_params, arg_no_initial_sync); daemonizer::init_options(hidden_options, desc_params); desc_params.add(hidden_options); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2cabb1ba5..e074ceed6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -72,14 +72,8 @@ else () include_directories(SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/gtest/include") endif (GTest_FOUND) -file(COPY - data/wallet_9svHk1.keys - data/wallet_9svHk1 - data/outputs - data/unsigned_monero_tx - data/signed_monero_tx - data/sha256sum - DESTINATION data) +message(STATUS "Copying test data directory...") +file(COPY data DESTINATION .) # Copy data directory from source root to build root if (CMAKE_BUILD_TYPE STREQUAL "fuzz" OR OSSFUZZ) add_subdirectory(fuzz) diff --git a/tests/data/wallet_00fd416a b/tests/data/wallet_00fd416a Binary files differnew file mode 100644 index 000000000..a1b7898e6 --- /dev/null +++ b/tests/data/wallet_00fd416a diff --git a/tests/data/wallet_00fd416a.keys b/tests/data/wallet_00fd416a.keys Binary files differnew file mode 100644 index 000000000..6908cce1b --- /dev/null +++ b/tests/data/wallet_00fd416a.keys diff --git a/tests/functional_tests/multisig.py b/tests/functional_tests/multisig.py index 980adc2df..3da983d69 100755 --- a/tests/functional_tests/multisig.py +++ b/tests/functional_tests/multisig.py @@ -37,100 +37,61 @@ import random from framework.daemon import Daemon from framework.wallet import Wallet -MULTISIG_PUB_ADDRS = [ - '45J58b7PmKJFSiNPFFrTdtfMcFGnruP7V4CMuRpX7NsH4j3jGHKAjo3YJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ3gr7sG', # 2/2 - '44G2TQNfsiURKkvxp7gbgaJY8WynZvANnhmyMAwv6WeEbAvyAWMfKXRhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5duN94i', # 2/3 - '41mro238grj56GnrWkakAKTkBy2yDcXYsUZ2iXCM9pe5Ueajd2RRc6Fhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5ief4ZP', # 3/3 - '44vZSprQKJQRFe6t1VHgU4ESvq2dv7TjBLVGE7QscKxMdFSiyyPCEV64NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6dakeff', # 3/4 - '47puypSwsV1gvUDratmX4y58fSwikXVehEiBhVLxJA1gRCxHyrRgTDr4NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6aRPj5U', # 2/4 - '4A8RnBQixry4VXkqeWhmg8L7vWJVDJj4FN9PV4E7Mgad5ZZ6LKQdn8dYJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ4S8RSB' # 1/2 +TEST_CASES = \ +[ +# M N Primary Address + [2, 2, '45J58b7PmKJFSiNPFFrTdtfMcFGnruP7V4CMuRpX7NsH4j3jGHKAjo3YJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ3gr7sG'], + [2, 3, '44G2TQNfsiURKkvxp7gbgaJY8WynZvANnhmyMAwv6WeEbAvyAWMfKXRhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5duN94i'], + [3, 3, '41mro238grj56GnrWkakAKTkBy2yDcXYsUZ2iXCM9pe5Ueajd2RRc6Fhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5ief4ZP'], + [3, 4, '44vZSprQKJQRFe6t1VHgU4ESvq2dv7TjBLVGE7QscKxMdFSiyyPCEV64NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6dakeff'], + [2, 4, '47puypSwsV1gvUDratmX4y58fSwikXVehEiBhVLxJA1gRCxHyrRgTDr4NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6aRPj5U'], + [1, 2, '4A8RnBQixry4VXkqeWhmg8L7vWJVDJj4FN9PV4E7Mgad5ZZ6LKQdn8dYJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ4S8RSB'] ] +PUB_ADDRS = [case[2] for case in TEST_CASES] + class MultisigTest(): def run_test(self): self.reset() - for pub_addr in MULTISIG_PUB_ADDRS: - self.mine(pub_addr, 4) + for pub_addr in PUB_ADDRS: + self.mine(pub_addr, 4) self.mine('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80) self.test_states() - self.fund_addrs_with_normal_wallet(MULTISIG_PUB_ADDRS) - - self.create_multisig_wallets(2, 2, '45J58b7PmKJFSiNPFFrTdtfMcFGnruP7V4CMuRpX7NsH4j3jGHKAjo3YJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ3gr7sG') - self.import_multisig_info([1, 0], 5) - txid = self.transfer([1, 0]) - self.import_multisig_info([0, 1], 6) - self.check_transaction(txid) - - self.remake_some_multisig_wallets_by_multsig_seed(2) - self.import_multisig_info([0, 1], 6) # six outputs, same as before - txid = self.transfer([0, 1]) - self.import_multisig_info([0, 1], 7) # seven outputs b/c we're dest plus change - self.check_transaction(txid) - - self.create_multisig_wallets(2, 3, '44G2TQNfsiURKkvxp7gbgaJY8WynZvANnhmyMAwv6WeEbAvyAWMfKXRhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5duN94i') - self.import_multisig_info([0, 2], 5) - txid = self.transfer([0, 2]) - self.import_multisig_info([0, 1, 2], 6) - self.check_transaction(txid) - - self.remake_some_multisig_wallets_by_multsig_seed(2) - self.import_multisig_info([0, 2], 6) # six outputs, same as before - txid = self.transfer([0, 2]) - self.import_multisig_info([0, 1, 2], 7) # seven outputs b/c we're dest plus change - self.check_transaction(txid) - - self.create_multisig_wallets(3, 3, '41mro238grj56GnrWkakAKTkBy2yDcXYsUZ2iXCM9pe5Ueajd2RRc6Fhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5ief4ZP') - self.import_multisig_info([2, 0, 1], 5) - txid = self.transfer([2, 1, 0]) - self.import_multisig_info([0, 2, 1], 6) - self.check_transaction(txid) - - self.remake_some_multisig_wallets_by_multsig_seed(3) - self.import_multisig_info([2, 0, 1], 6) # six outputs, same as before - txid = self.transfer([2, 1, 0]) - self.import_multisig_info([0, 2, 1], 7) # seven outputs b/c we're dest plus change - self.check_transaction(txid) - - self.create_multisig_wallets(3, 4, '44vZSprQKJQRFe6t1VHgU4ESvq2dv7TjBLVGE7QscKxMdFSiyyPCEV64NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6dakeff') - self.import_multisig_info([0, 2, 3], 5) - txid = self.transfer([0, 2, 3]) - self.import_multisig_info([0, 1, 2, 3], 6) - self.check_transaction(txid) - - self.remake_some_multisig_wallets_by_multsig_seed(3) - self.import_multisig_info([0, 2, 3], 6) # six outputs, same as before - txid = self.transfer([0, 2, 3]) - self.import_multisig_info([0, 1, 2, 3], 7) # seven outputs b/c we're dest plus change - self.check_transaction(txid) - - self.create_multisig_wallets(2, 4, '47puypSwsV1gvUDratmX4y58fSwikXVehEiBhVLxJA1gRCxHyrRgTDr4NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6aRPj5U') - self.import_multisig_info([1, 2], 5) - txid = self.transfer([1, 2]) - self.import_multisig_info([0, 1, 2, 3], 6) - self.check_transaction(txid) - txid = self.try_transfer_frozen([2, 3]) - self.import_multisig_info([0, 1, 2, 3], 7) - self.check_transaction(txid) - - self.remake_some_multisig_wallets_by_multsig_seed(2) - self.import_multisig_info([0, 1, 2, 3], 6) # six outputs, same as before - txid = self.transfer([2, 3]) - self.import_multisig_info([0, 1, 2, 3], 7) # seven outputs b/c we're dest plus change - self.check_transaction(txid) - - self.create_multisig_wallets(1, 2, '4A8RnBQixry4VXkqeWhmg8L7vWJVDJj4FN9PV4E7Mgad5ZZ6LKQdn8dYJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ4S8RSB') - self.import_multisig_info([0, 1], 5) - txid = self.transfer([0]) - self.import_multisig_info([0, 1], 6) - self.check_transaction(txid) - - self.remake_some_multisig_wallets_by_multsig_seed(1) - self.import_multisig_info([0, 1], 6) # six outputs, same as before - txid = self.transfer([1]) - self.import_multisig_info([0, 1], 7) # seven outputs b/c we're dest plus change - self.check_transaction(txid) + self.fund_addrs_with_normal_wallet(PUB_ADDRS) + + for M, N, pub_addr in TEST_CASES: + assert M <= N + shuffled_participants = list(range(N)) + random.shuffle(shuffled_participants) + shuffled_signers = shuffled_participants[:M] + + expected_outputs = 5 # each wallet owns four mined outputs & one transferred output + + # Create multisig wallet and test transferring + self.create_multisig_wallets(M, N, pub_addr) + self.import_multisig_info(shuffled_signers if M != 1 else shuffled_participants, expected_outputs) + txid = self.transfer(shuffled_signers) + expected_outputs += 1 + self.import_multisig_info(shuffled_participants, expected_outputs) + self.check_transaction(txid) + + # If more than 1 signer, try to freeze key image of one signer, make tx using that key + # image on another signer, then have first signer sign multisg_txset. Should fail + if M != 1: + txid = self.try_transfer_frozen(shuffled_signers) + expected_outputs += 1 + self.import_multisig_info(shuffled_participants, expected_outputs) + self.check_transaction(txid) + + # Recreate wallet from multisig seed and test transferring + self.remake_some_multisig_wallets_by_multsig_seed(M) + self.import_multisig_info(shuffled_signers if M != 1 else shuffled_participants, expected_outputs) + txid = self.transfer(shuffled_signers) + expected_outputs += 1 + self.import_multisig_info(shuffled_participants, expected_outputs) + self.check_transaction(txid) def reset(self): print('Resetting blockchain') @@ -262,10 +223,10 @@ class MultisigTest(): def remake_some_multisig_wallets_by_multsig_seed(self, threshold): N = len(self.wallet) - signers_to_remake = set() num_signers_to_remake = random.randint(1, N) # Do at least one - while len(signers_to_remake) < num_signers_to_remake: - signers_to_remake.add(random.randint(0, N - 1)) + signers_to_remake = list(range(N)) + random.shuffle(signers_to_remake) + signers_to_remake = signers_to_remake[:num_signers_to_remake] for i in signers_to_remake: print("Remaking {}/{} multsig wallet from multisig seed: #{}".format(threshold, N, i+1)) @@ -501,7 +462,7 @@ class MultisigTest(): print("Attemping to sign with frozen key image. This should fail") try: res = self.wallet[signers[1]].sign_multisig(multisig_txset) - raise ValueError('sign_multisig should not have succeeded w/ fronzen enotes') + raise ValueError('sign_multisig should not have succeeded w/ frozen enotes') except AssertionError: pass diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 147b38dd4..fec36803e 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -97,6 +97,7 @@ set(unit_tests_sources output_selection.cpp vercmp.cpp ringdb.cpp + wallet_storage.cpp wipeable_string.cpp is_hdd.cpp aligned.cpp diff --git a/tests/unit_tests/long_term_block_weight.cpp b/tests/unit_tests/long_term_block_weight.cpp index 973055097..57740cf9d 100644 --- a/tests/unit_tests/long_term_block_weight.cpp +++ b/tests/unit_tests/long_term_block_weight.cpp @@ -407,3 +407,38 @@ TEST(long_term_block_weight, long_growth_spike_and_drop) ASSERT_GT(long_term_effective_median_block_weight, 300000 * 1.07); ASSERT_LT(long_term_effective_median_block_weight, 300000 * 1.09); } + +TEST(long_term_block_weight, cache_matches_true_value) +{ + PREFIX(16); + + // Add big blocks to increase the block weight limit + for (uint64_t h = 0; h <= 2000; ++h) + { + size_t w = bc->get_current_cumulative_block_weight_limit(); + uint64_t ltw = bc->get_next_long_term_block_weight(w); + bc->get_db().add_block(std::make_pair(cryptonote::block(), ""), w, ltw, h, h, {}); + bc->update_next_cumulative_weight_limit(); + } + + ASSERT_GT(bc->get_current_cumulative_block_weight_limit() * 10/17 , 300000); + + // Add small blocks to the top of the chain + for (uint64_t h = 2000; h <= 5001; ++h) + { + size_t w = (bc->get_current_cumulative_block_weight_median() * 10/17) - 1000; + uint64_t ltw = bc->get_next_long_term_block_weight(w); + bc->get_db().add_block(std::make_pair(cryptonote::block(), ""), w, ltw, h, h, {}); + bc->update_next_cumulative_weight_limit(); + } + + // get the weight limit + uint64_t weight_limit = bc->get_current_cumulative_block_weight_limit(); + // refresh the cache + bc->m_long_term_block_weights_cache_rolling_median.clear(); + bc->get_long_term_block_weight_median(bc->get_db().height() - TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW, TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW); + bc->update_next_cumulative_weight_limit(); + + // make sure the weight limit is the same + ASSERT_EQ(weight_limit, bc->get_current_cumulative_block_weight_limit()); +} diff --git a/tests/unit_tests/wallet_storage.cpp b/tests/unit_tests/wallet_storage.cpp new file mode 100644 index 000000000..dacaff960 --- /dev/null +++ b/tests/unit_tests/wallet_storage.cpp @@ -0,0 +1,266 @@ +// Copyright (c) 2023, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "unit_tests_utils.h" +#include "gtest/gtest.h" + +#include "file_io_utils.h" +#include "wallet/wallet2.h" + +using namespace boost::filesystem; +using namespace epee::file_io_utils; + +static constexpr const char WALLET_00fd416a_PRIMARY_ADDRESS[] = + "45p2SngJAPSJbqSiUvYfS3BfhEdxZmv8pDt25oW1LzxrZv9Uq6ARagiFViMGUE3gJk5VPWingCXVf1p2tyAy6SUeSHPhbve"; + +TEST(wallet_storage, store_to_file2file) +{ + const path source_wallet_file = unit_test::data_dir / "wallet_00fd416a"; + const path interm_wallet_file = unit_test::data_dir / "wallet_00fd416a_copy_file2file"; + const path target_wallet_file = unit_test::data_dir / "wallet_00fd416a_new_file2file"; + + ASSERT_TRUE(is_file_exist(source_wallet_file.string())); + ASSERT_TRUE(is_file_exist(source_wallet_file.string() + ".keys")); + + copy_file(source_wallet_file, interm_wallet_file, copy_option::overwrite_if_exists); + copy_file(source_wallet_file.string() + ".keys", interm_wallet_file.string() + ".keys", copy_option::overwrite_if_exists); + + ASSERT_TRUE(is_file_exist(interm_wallet_file.string())); + ASSERT_TRUE(is_file_exist(interm_wallet_file.string() + ".keys")); + + if (is_file_exist(target_wallet_file.string())) + remove(target_wallet_file); + if (is_file_exist(target_wallet_file.string() + ".keys")) + remove(target_wallet_file.string() + ".keys"); + ASSERT_FALSE(is_file_exist(target_wallet_file.string())); + ASSERT_FALSE(is_file_exist(target_wallet_file.string() + ".keys")); + + epee::wipeable_string password("beepbeep"); + + const auto files_are_expected = [&]() + { + EXPECT_FALSE(is_file_exist(interm_wallet_file.string())); + EXPECT_FALSE(is_file_exist(interm_wallet_file.string() + ".keys")); + EXPECT_TRUE(is_file_exist(target_wallet_file.string())); + EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys")); + }; + + { + tools::wallet2 w; + w.load(interm_wallet_file.string(), password); + const std::string primary_address = w.get_address_as_str(); + EXPECT_EQ(WALLET_00fd416a_PRIMARY_ADDRESS, primary_address); + w.store_to(target_wallet_file.string(), password); + files_are_expected(); + } + + files_are_expected(); + + { + tools::wallet2 w; + w.load(target_wallet_file.string(), password); + const std::string primary_address = w.get_address_as_str(); + EXPECT_EQ(WALLET_00fd416a_PRIMARY_ADDRESS, primary_address); + w.store_to("", ""); + files_are_expected(); + } + + files_are_expected(); +} + +TEST(wallet_storage, store_to_mem2file) +{ + const path target_wallet_file = unit_test::data_dir / "wallet_mem2file"; + + if (is_file_exist(target_wallet_file.string())) + remove(target_wallet_file); + if (is_file_exist(target_wallet_file.string() + ".keys")) + remove(target_wallet_file.string() + ".keys"); + ASSERT_FALSE(is_file_exist(target_wallet_file.string())); + ASSERT_FALSE(is_file_exist(target_wallet_file.string() + ".keys")); + + epee::wipeable_string password("beepbeep2"); + + { + tools::wallet2 w; + w.generate("", password); + w.store_to(target_wallet_file.string(), password); + + EXPECT_TRUE(is_file_exist(target_wallet_file.string())); + EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys")); + } + + EXPECT_TRUE(is_file_exist(target_wallet_file.string())); + EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys")); + + { + tools::wallet2 w; + w.load(target_wallet_file.string(), password); + + EXPECT_TRUE(is_file_exist(target_wallet_file.string())); + EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys")); + } + + EXPECT_TRUE(is_file_exist(target_wallet_file.string())); + EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys")); +} + +TEST(wallet_storage, change_password_same_file) +{ + const path source_wallet_file = unit_test::data_dir / "wallet_00fd416a"; + const path interm_wallet_file = unit_test::data_dir / "wallet_00fd416a_copy_change_password_same"; + + ASSERT_TRUE(is_file_exist(source_wallet_file.string())); + ASSERT_TRUE(is_file_exist(source_wallet_file.string() + ".keys")); + + copy_file(source_wallet_file, interm_wallet_file, copy_option::overwrite_if_exists); + copy_file(source_wallet_file.string() + ".keys", interm_wallet_file.string() + ".keys", copy_option::overwrite_if_exists); + + ASSERT_TRUE(is_file_exist(interm_wallet_file.string())); + ASSERT_TRUE(is_file_exist(interm_wallet_file.string() + ".keys")); + + epee::wipeable_string old_password("beepbeep"); + epee::wipeable_string new_password("meepmeep"); + + { + tools::wallet2 w; + w.load(interm_wallet_file.string(), old_password); + const std::string primary_address = w.get_address_as_str(); + EXPECT_EQ(WALLET_00fd416a_PRIMARY_ADDRESS, primary_address); + w.change_password(w.get_wallet_file(), old_password, new_password); + } + + { + tools::wallet2 w; + w.load(interm_wallet_file.string(), new_password); + const std::string primary_address = w.get_address_as_str(); + EXPECT_EQ(WALLET_00fd416a_PRIMARY_ADDRESS, primary_address); + } + + { + tools::wallet2 w; + EXPECT_THROW(w.load(interm_wallet_file.string(), old_password), tools::error::invalid_password); + } +} + +TEST(wallet_storage, change_password_different_file) +{ + const path source_wallet_file = unit_test::data_dir / "wallet_00fd416a"; + const path interm_wallet_file = unit_test::data_dir / "wallet_00fd416a_copy_change_password_diff"; + const path target_wallet_file = unit_test::data_dir / "wallet_00fd416a_new_change_password_diff"; + + ASSERT_TRUE(is_file_exist(source_wallet_file.string())); + ASSERT_TRUE(is_file_exist(source_wallet_file.string() + ".keys")); + + copy_file(source_wallet_file, interm_wallet_file, copy_option::overwrite_if_exists); + copy_file(source_wallet_file.string() + ".keys", interm_wallet_file.string() + ".keys", copy_option::overwrite_if_exists); + + ASSERT_TRUE(is_file_exist(interm_wallet_file.string())); + ASSERT_TRUE(is_file_exist(interm_wallet_file.string() + ".keys")); + + if (is_file_exist(target_wallet_file.string())) + remove(target_wallet_file); + if (is_file_exist(target_wallet_file.string() + ".keys")) + remove(target_wallet_file.string() + ".keys"); + ASSERT_FALSE(is_file_exist(target_wallet_file.string())); + ASSERT_FALSE(is_file_exist(target_wallet_file.string() + ".keys")); + + epee::wipeable_string old_password("beepbeep"); + epee::wipeable_string new_password("meepmeep"); + + { + tools::wallet2 w; + w.load(interm_wallet_file.string(), old_password); + const std::string primary_address = w.get_address_as_str(); + EXPECT_EQ(WALLET_00fd416a_PRIMARY_ADDRESS, primary_address); + w.change_password(target_wallet_file.string(), old_password, new_password); + } + + EXPECT_FALSE(is_file_exist(interm_wallet_file.string())); + EXPECT_FALSE(is_file_exist(interm_wallet_file.string() + ".keys")); + EXPECT_TRUE(is_file_exist(target_wallet_file.string())); + EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys")); + + { + tools::wallet2 w; + w.load(target_wallet_file.string(), new_password); + const std::string primary_address = w.get_address_as_str(); + EXPECT_EQ(WALLET_00fd416a_PRIMARY_ADDRESS, primary_address); + } +} + +TEST(wallet_storage, change_password_in_memory) +{ + const epee::wipeable_string password1("monero"); + const epee::wipeable_string password2("means money"); + const epee::wipeable_string password_wrong("is traceable"); + + tools::wallet2 w; + w.generate("", password1); + const std::string primary_address_1 = w.get_address_as_str(); + w.change_password("", password1, password2); + const std::string primary_address_2 = w.get_address_as_str(); + EXPECT_EQ(primary_address_1, primary_address_2); + + EXPECT_THROW(w.change_password("", password_wrong, password1), tools::error::invalid_password); +} + +TEST(wallet_storage, change_password_mem2file) +{ + const path target_wallet_file = unit_test::data_dir / "wallet_change_password_mem2file"; + + if (is_file_exist(target_wallet_file.string())) + remove(target_wallet_file); + if (is_file_exist(target_wallet_file.string() + ".keys")) + remove(target_wallet_file.string() + ".keys"); + ASSERT_FALSE(is_file_exist(target_wallet_file.string())); + ASSERT_FALSE(is_file_exist(target_wallet_file.string() + ".keys")); + + const epee::wipeable_string password1("https://safecurves.cr.yp.to/rigid.html"); + const epee::wipeable_string password2( + "https://csrc.nist.gov/csrc/media/projects/crypto-standards-development-process/documents/dualec_in_x982_and_sp800-90.pdf"); + + std::string primary_address_1, primary_address_2; + { + tools::wallet2 w; + w.generate("", password1); + primary_address_1 = w.get_address_as_str(); + w.change_password(target_wallet_file.string(), password1, password2); + } + + EXPECT_TRUE(is_file_exist(target_wallet_file.string())); + EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys")); + + { + tools::wallet2 w; + w.load(target_wallet_file.string(), password2); + primary_address_2 = w.get_address_as_str(); + } + + EXPECT_EQ(primary_address_1, primary_address_2); +} |