diff --git a/CMakeLists.txt b/CMakeLists.txt index 10aa698a..4aa125a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -187,6 +187,7 @@ else() set(LLVM_USE_LINKER "gold") else() set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=gold") + link_libraries("$<$,$,9.0>>:-lstdc++fs>") # GCC < 9 requires additional linking for std::filesystem. Remove after stop supporting GCC 8.x -- sowle endif() endif() if(CMAKE_C_COMPILER_ID STREQUAL "GNU" AND NOT (CMAKE_C_COMPILER_VERSION VERSION_LESS 4.8)) @@ -248,7 +249,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "iOS") #set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] "${__iphonesimulator_archs}") elseif(CMAKE_SYSTEM_NAME STREQUAL "Android") if(CAKEWALLET) - find_package(Boost ${ZANO_BOOST_MIN_VER} REQUIRED COMPONENTS system filesystem thread timer date_time chrono regex serialization atomic program_options locale) + find_package(Boost ${ZANO_BOOST_MIN_VER} REQUIRED COMPONENTS system filesystem locale thread timer date_time chrono regex serialization atomic program_options) else() set(Boost_LIBRARY_DIRS "${Boost_LIBRARY_DIRS}/${CMAKE_ANDROID_ARCH_ABI}/") set(Boost_LIBRARIES "${Boost_LIBRARY_DIRS}libboost_system.a;${Boost_LIBRARY_DIRS}libboost_filesystem.a;${Boost_LIBRARY_DIRS}libboost_thread.a;${Boost_LIBRARY_DIRS}libboost_timer.a;${Boost_LIBRARY_DIRS}libboost_date_time.a;${Boost_LIBRARY_DIRS}libboost_chrono.a;${Boost_LIBRARY_DIRS}libboost_regex.a;${Boost_LIBRARY_DIRS}libboost_serialization.a;${Boost_LIBRARY_DIRS}libboost_atomic.a;${Boost_LIBRARY_DIRS}libboost_program_options.a") @@ -256,9 +257,9 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Android") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fPIC") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fPIC") elseif(APPLE) - find_package(Boost ${ZANO_BOOST_MIN_VER} REQUIRED COMPONENTS system filesystem thread timer date_time chrono regex serialization atomic program_options locale) + find_package(Boost ${ZANO_BOOST_MIN_VER} REQUIRED COMPONENTS system filesystem locale thread timer date_time chrono regex serialization atomic program_options) else() - find_package(Boost ${ZANO_BOOST_MIN_VER} REQUIRED COMPONENTS system filesystem thread timer date_time chrono regex serialization atomic program_options locale log) + find_package(Boost ${ZANO_BOOST_MIN_VER} REQUIRED COMPONENTS system filesystem locale thread timer date_time chrono regex serialization atomic program_options log) endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 27739818..e6bdcea2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -115,13 +115,17 @@ else() set_source_files_properties("crypto/chacha8_stream.c" PROPERTIES COMPILE_FLAGS "-Wno-sign-compare -Wno-strict-prototypes") endif() +if(WIN32) + set(BCRYPT_LIB bcrypt) +endif() + add_library(crypto ${CRYPTO}) if(USE_BITCOIN_SECP256K1_FOR_ECDSA) add_dependencies(crypto secp256k1) - target_link_libraries(crypto secp256k1) + target_link_libraries(crypto secp256k1 ${BCRYPT_LIB}) else() add_dependencies(crypto OpenSSL::Crypto) - target_link_libraries(crypto OpenSSL::Crypto) + target_link_libraries(crypto OpenSSL::Crypto ${BCRYPT_LIB}) endif() add_library(currency_core ${CURRENCY_CORE}) diff --git a/src/common/pod_array_file_container.h b/src/common/pod_array_file_container.h index 7c168d79..e165480c 100644 --- a/src/common/pod_array_file_container.h +++ b/src/common/pod_array_file_container.h @@ -67,6 +67,7 @@ namespace tools *p_reason = std::string("file was corrupted, truncated: ") + epee::string_tools::num_to_string_fast(file_size) + " -> " + epee::string_tools::num_to_string_fast(corrected_size); } + m_filename = filename; return true; } @@ -77,7 +78,7 @@ namespace tools bool push_back(const pod_t& item) { - if (!m_stream.is_open() || (m_stream.rdstate() != std::ios::goodbit && m_stream.rdstate() != std::ios::eofbit)) + if (!is_opened_and_in_good_state()) return false; m_stream.seekp(0, std::ios_base::end); @@ -93,7 +94,7 @@ namespace tools bool get_item(size_t index, pod_t& result) const { - if (!m_stream.is_open() || (m_stream.rdstate() != std::ios::goodbit && m_stream.rdstate() != std::ios::eofbit)) + if (!is_opened_and_in_good_state()) return false; size_t offset = index * sizeof result; @@ -108,20 +109,42 @@ namespace tools size_t size_bytes() const { - if (!m_stream.is_open() || (m_stream.rdstate() != std::ios::goodbit && m_stream.rdstate() != std::ios::eofbit)) + if (!is_opened_and_in_good_state()) return 0; m_stream.seekg(0, std::ios_base::end); return m_stream.tellg(); } + bool is_opened_and_in_good_state() const + { + if (!m_stream.is_open()) + return false; + if (m_stream.rdstate() != std::ios::goodbit && m_stream.rdstate() != std::ios::eofbit) + return false; + return true; + } + size_t size() const { return size_bytes() / sizeof(pod_t); } + bool clear() + { + if (!is_opened_and_in_good_state()) + return false; + + // close and re-open stream with trunc bit + m_stream.close(); + m_stream.open(m_filename, std::ios::binary | std::ios::trunc | std::ios::in | std::ios::out); + + return is_opened_and_in_good_state(); + } + private: mutable boost::filesystem::fstream m_stream; + std::wstring m_filename; }; } // namespace tools diff --git a/src/connectivity_tool/conn_tool.cpp b/src/connectivity_tool/conn_tool.cpp index 71da0f4c..e5aa90a8 100644 --- a/src/connectivity_tool/conn_tool.cpp +++ b/src/connectivity_tool/conn_tool.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2024 Zano Project +// Copyright (c) 2014-2025 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying @@ -57,8 +57,7 @@ namespace const command_line::arg_descriptor arg_generate_genesis ("generate-genesis", "Generate genesis coinbase based on config file"); const command_line::arg_descriptor arg_genesis_split_amount ( "genesis-split-amount", "Set split amount for generating genesis block"); const command_line::arg_descriptor arg_get_info_flags ( "getinfo-flags-hex", "Set of bits for rpc-get-daemon-info", ""); - const command_line::arg_descriptor arg_set_peer_log_level ( "set-peer-log-level", "Set log level for remote peer"); - const command_line::arg_descriptor arg_download_peer_log ( "download-peer-log", "Download log from remote peer [,]"); + const command_line::arg_descriptor arg_get_anonymized_peers( "get-anonymized-peers", "Retrieves anonymized peers connected to the specified peer."); const command_line::arg_descriptor arg_do_consloe_log ( "do-console-log", "Tool generates debug console output(debug purposes)"); const command_line::arg_descriptor arg_generate_integrated_address ( "generate-integrated-address", "Tool create integrated address from simple address and payment_id"); const command_line::arg_descriptor arg_pack_file ("pack-file", "perform gzip-packing and calculate hash for a given file"); @@ -746,23 +745,23 @@ bool get_private_key(crypto::secret_key& pk, po::variables_map& vm) } else { - key_str = get_password("Enter maintain private key:"); + key_str = get_password("Enter maintenance private key:"); } if(!string_tools::hex_to_pod(key_str, pk)) { - std::cout << "ERROR: wrong secret key set" << ENDL; + std::cout << "ERROR: incorrect private key entered" << ENDL; return false; } crypto::public_key pubkey = AUTO_VAL_INIT(pubkey); if(!crypto::secret_key_to_public_key(pk, pubkey)) { - std::cout << "ERROR: wrong secret key set(secret_key_to_public_key failed)" << ENDL; + std::cout << "ERROR: wrong private key (secret_key_to_public_key failed)" << ENDL; return false; } if( pubkey != tools::get_public_key_from_string(P2P_MAINTAINERS_PUB_KEY)) { - std::cout << "ERROR: wrong secret key set(public keys not match)" << ENDL; + std::cout << "ERROR: wrong pritvate key (public key missmatch)" << ENDL; return false; } return true; @@ -827,7 +826,7 @@ bool handle_increment_build_no(po::variables_map& vm) bool handle_update_maintainers_info(po::variables_map& vm) { log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL); - size_t rpc_port = RPC_DEFAULT_PORT; + [[maybe_unused]] size_t rpc_port = RPC_DEFAULT_PORT; if(!command_line::has_arg(vm, arg_rpc_port)) { std::cout << "ERROR: rpc port not set" << ENDL; @@ -923,154 +922,34 @@ bool invoke_debug_command(po::variables_map& vm, const crypto::secret_key& sk, n return net_utils::invoke_remote_command2(command_t::ID, req, rsp, transport); } //--------------------------------------------------------------------------------------------------------------- -bool handle_set_peer_log_level(po::variables_map& vm) +bool handle_get_anonymized_peers(po::variables_map& vm) { - crypto::secret_key sk = AUTO_VAL_INIT(sk); + crypto::secret_key sk{}; if (!get_private_key(sk, vm)) { std::cout << "ERROR: secret key error" << ENDL; return false; } - int64_t log_level = command_line::get_arg(vm, arg_set_peer_log_level); - if (log_level < LOG_LEVEL_0 || log_level > LOG_LEVEL_MAX) - { - std::cout << "Error: invalid log level value: " << log_level << ENDL; - return false; - } - net_utils::levin_client2 transport; peerid_type peer_id = 0; - COMMAND_SET_LOG_LEVEL::request req = AUTO_VAL_INIT(req); - req.new_log_level = log_level; + COMMAND_REQUEST_ANONYMIZED_PEERS::request req{}; - COMMAND_SET_LOG_LEVEL::response rsp = AUTO_VAL_INIT(rsp); - if (!invoke_debug_command(vm, sk, transport, peer_id, req, rsp)) + COMMAND_REQUEST_ANONYMIZED_PEERS::response rsp{}; + if (!invoke_debug_command(vm, sk, transport, peer_id, req, rsp)) { - std::cout << "ERROR: invoking COMMAND_SET_LOG_LEVEL failed" << ENDL; + std::cout << "ERROR: invoking COMMAND_REQUEST_ANONYMIZED_PEERS failed" << ENDL; return false; } - std::cout << "OK! Log level changed: " << rsp.old_log_level << " -> " << rsp.current_log_level << ENDL; + std::cout << "Success." << ENDL << ENDL; + + std::cout << epee::serialization::store_t_to_json(rsp); return true; } //--------------------------------------------------------------------------------------------------------------- -bool handle_download_peer_log(po::variables_map& vm) -{ - crypto::secret_key sk = AUTO_VAL_INIT(sk); - if (!get_private_key(sk, vm)) - { - std::cout << "ERROR: secret key error" << ENDL; - return false; - } - - int64_t start_offset_signed = 0; - int64_t count = -1; - - std::string arg_str = command_line::get_arg(vm, arg_download_peer_log); - size_t comma_pos = arg_str.find(','); - if (comma_pos != std::string::npos) - { - // count is specified - if (!epee::string_tools::string_to_num_fast(arg_str.substr(comma_pos + 1), count) || count < 0) - { - std::cout << "ERROR: invalid argument: " << arg_str << ENDL; - return false; - } - arg_str.erase(comma_pos); - } - if (!epee::string_tools::string_to_num_fast(arg_str, start_offset_signed) || start_offset_signed < 0) - { - std::cout << "ERROR: couldn't parse start_offset: " << arg_str << ENDL; - return false; - } - uint64_t start_offset = static_cast(start_offset_signed); - - net_utils::levin_client2 transport; - peerid_type peer_id = 0; - - COMMAND_REQUEST_LOG::request req = AUTO_VAL_INIT(req); - COMMAND_REQUEST_LOG::response rsp = AUTO_VAL_INIT(rsp); - if (!invoke_debug_command(vm, sk, transport, peer_id, req, rsp) || !rsp.error.empty()) - { - std::cout << "ERROR: invoking COMMAND_REQUEST_LOG failed: " << rsp.error << ENDL; - return false; - } - - std::cout << "Current log level: " << rsp.current_log_level << ENDL; - std::cout << "Current log size: " << rsp.current_log_size << ENDL; - - if (start_offset == 0 && count == 0) - return true; // a caller wanted to just get the info, end of story - - if (start_offset >= rsp.current_log_size) - { - std::cout << "ERROR: invalid start offset: " << start_offset << ", log size: " << rsp.current_log_size << ENDL; - return false; - } - - std::cout << "Downloading..." << ENDL; - - std::string local_filename = tools::get_default_data_dir() + "/log_" + epee::string_tools::num_to_string_fast(peer_id) + ".log"; - std::ofstream log{ local_filename, std::ifstream::binary }; - if (!log) - { - std::cout << "Couldn't open " << local_filename << " for writing." << ENDL; - return false; - } - - const uint64_t chunk_size = 1024 * 1024 * 5; - uint64_t end_offset = start_offset; - while (true) - { - req.log_chunk_offset = end_offset; - req.log_chunk_size = std::min(chunk_size, rsp.current_log_size - req.log_chunk_offset); - - if (count > 0) - { - uint64_t bytes_left = count + start_offset - end_offset; - req.log_chunk_size = std::min(req.log_chunk_size, bytes_left); - } - - if (req.log_chunk_size == 0) - break; - - std::this_thread::sleep_for(std::chrono::seconds(1)); - - if (!invoke_debug_command(vm, sk, transport, peer_id, req, rsp) || !rsp.error.empty()) - { - std::cout << "ERROR: invoking COMMAND_REQUEST_LOG failed: " << rsp.error << ENDL; - return false; - } - - if (!epee::zlib_helper::unpack(rsp.log_chunk)) - { - std::cout << "ERROR: zip unpack failed" << ENDL; - return false; - } - - if (rsp.log_chunk.size() != req.log_chunk_size) - { - std::cout << "ERROR: unpacked size: " << rsp.log_chunk.size() << ", requested: " << req.log_chunk_size << ENDL; - return false; - } - - log.write(rsp.log_chunk.c_str(), rsp.log_chunk.size()); - - end_offset += req.log_chunk_size; - - std::cout << end_offset - start_offset << " bytes downloaded" << ENDL; - } - - std::cout << "Remote log from offset " << start_offset << " to offset " << end_offset << " (" << end_offset - start_offset << " bytes) " << - "was successfully downloaded to " << local_filename << ENDL; - - return true; -} - - bool handle_generate_integrated_address(po::variables_map& vm) { std::string add_and_payment_id = command_line::get_arg(vm, arg_generate_integrated_address); @@ -1326,8 +1205,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_genesis_split_amount); command_line::add_arg(desc_params, arg_get_info_flags); command_line::add_arg(desc_params, arg_log_journal_len); - command_line::add_arg(desc_params, arg_set_peer_log_level); - command_line::add_arg(desc_params, arg_download_peer_log); + command_line::add_arg(desc_params, arg_get_anonymized_peers); command_line::add_arg(desc_params, arg_do_consloe_log); command_line::add_arg(desc_params, arg_generate_integrated_address); command_line::add_arg(desc_params, arg_pack_file); @@ -1395,13 +1273,9 @@ int main(int argc, char* argv[]) { return generate_genesis(command_line::get_arg(vm, arg_generate_genesis), 10000000000000000) ? EXIT_SUCCESS : EXIT_FAILURE; } - else if (command_line::has_arg(vm, arg_set_peer_log_level)) + else if (command_line::has_arg(vm, arg_get_anonymized_peers) && command_line::get_arg(vm, arg_get_anonymized_peers)) { - return handle_set_peer_log_level(vm) ? EXIT_SUCCESS : EXIT_FAILURE; - } - else if (command_line::has_arg(vm, arg_download_peer_log)) - { - return handle_download_peer_log(vm) ? EXIT_SUCCESS : EXIT_FAILURE; + return handle_get_anonymized_peers(vm) ? EXIT_SUCCESS : EXIT_FAILURE; } else if (command_line::has_arg(vm, arg_generate_integrated_address)) { diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp index 37701cd7..b2b7c4bb 100644 --- a/src/crypto/crypto.cpp +++ b/src/crypto/crypto.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022 Zano Project +// Copyright (c) 2014-2025 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying @@ -35,32 +35,20 @@ namespace crypto { const key_image I = *reinterpret_cast(&I_); const key_image L = *reinterpret_cast(&L_); - struct random_init_singleton - { - random_init_singleton() - { - grant_random_initialize(); - } - }; - - random_init_singleton init_rand; //place initializer here to avoid grant_random_initialize first call after threads will be possible(local static variables init is not thread-safe) - - using std::abort; - using std::int32_t; - using std::int64_t; - using std::lock_guard; - using std::mutex; - using std::size_t; - using std::uint32_t; - using std::uint64_t; - extern "C" { #include "crypto-ops.h" #include "random.h" } - mutex random_lock; + std::mutex& random_lock_accessor() noexcept + { + // this is a thread-safe approach + // note section 6.7: "If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization." + static std::mutex random_lock; + return random_lock; + } + static inline unsigned char *operator &(ec_point &point) { return &reinterpret_cast(point); @@ -78,9 +66,10 @@ namespace crypto { return &reinterpret_cast(scalar); } - static inline void random_scalar(ec_scalar &res) { + static inline void random_scalar_no_lock(ec_scalar &res) + { unsigned char tmp[64]; - generate_random_bytes(64, tmp); + generate_random_bytes_no_lock(64, tmp); sc_reduce(tmp); memcpy(&res, tmp, 32); } @@ -118,10 +107,11 @@ namespace crypto { sc_reduce32(&res); } - void crypto_ops::generate_keys(public_key &pub, secret_key &sec) { - lock_guard lock(random_lock); + void crypto_ops::generate_keys(public_key &pub, secret_key &sec) + { + std::lock_guard lock(random_lock_accessor()); ge_p3 point; - random_scalar(sec); + random_scalar_no_lock(sec); ge_scalarmult_base(&point, &sec); ge_p3_tobytes(&pub, &point); } @@ -239,7 +229,7 @@ namespace crypto { }; void crypto_ops::generate_signature(const hash &prefix_hash, const public_key &pub, const secret_key &sec, signature &sig) { - lock_guard lock(random_lock); + std::lock_guard lock(random_lock_accessor()); ge_p3 tmp3; ec_scalar k; s_comm buf; @@ -255,7 +245,7 @@ namespace crypto { #endif buf.h = prefix_hash; buf.key = pub; - random_scalar(k); + random_scalar_no_lock(k); ge_scalarmult_base(&tmp3, &k); ge_p3_tobytes(&buf.comm, &tmp3); hash_to_scalar(&buf, sizeof(s_comm), sig.c); @@ -294,6 +284,7 @@ namespace crypto { } void crypto_ops::generate_key_image(const public_key &pub, const secret_key &sec, key_image &image) { + // image = sec * 8 * ge_fromfe_frombytes_vartime(cn_fast_hash(pub)) = sec * Hp( pub ) ge_p3 point; ge_p2 point2; crypto_assert(sc_check(&sec) == 0); @@ -324,7 +315,7 @@ POP_VS_WARNINGS const public_key *const *pubs, size_t pubs_count, const secret_key &sec, size_t sec_index, signature *sig) { - lock_guard lock(random_lock); + std::lock_guard lock(random_lock_accessor()); size_t i; ge_p3 image_unp; ge_dsmp image_pre; @@ -361,15 +352,15 @@ POP_VS_WARNINGS ge_p2 tmp2; ge_p3 tmp3; if (i == sec_index) { - random_scalar(k); + random_scalar_no_lock(k); ge_scalarmult_base(&tmp3, &k); ge_p3_tobytes(&buf->ab[i].a, &tmp3); hash_to_ec(*pubs[i], tmp3); ge_scalarmult(&tmp2, &k, &tmp3); ge_tobytes(&buf->ab[i].b, &tmp2); } else { - random_scalar(sig[i].c); - random_scalar(sig[i].r); + random_scalar_no_lock(sig[i].c); + random_scalar_no_lock(sig[i].r); if (ge_frombytes_vartime(&tmp3, &*pubs[i]) != 0) { abort(); } diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index 160929f8..9433ad4f 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018 Zano Project +// Copyright (c) 2014-2025 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying @@ -31,7 +31,7 @@ namespace crypto { #include "random.h" } - extern std::mutex random_lock; + std::mutex& random_lock_accessor() noexcept; #pragma pack(push, 1) POD_CLASS ec_point { @@ -114,17 +114,27 @@ namespace crypto { }; + // thread-safe version + inline void generate_random_bytes(size_t size, void* p_data) + { + std::lock_guard lock(random_lock_accessor()); + generate_random_bytes_no_lock(size, p_data); + } + + /* Generate a value filled with random bytes. */ template - typename std::enable_if::value, T>::type rand() { + typename std::enable_if::value, T>::type rand() + { typename std::remove_cv::type res; - std::lock_guard lock(random_lock); - generate_random_bytes(sizeof(T), &res); + std::lock_guard lock(random_lock_accessor()); + generate_random_bytes_no_lock(sizeof(T), &res); return res; } /* An adapter, to be used with std::shuffle, etc. + * Uses thread-safe crypto::rand<>(). */ struct uniform_random_bit_generator { @@ -302,10 +312,11 @@ namespace crypto { } // namespace crypto POD_MAKE_HASHABLE(crypto, public_key) +POD_MAKE_LESS_OPERATOR(crypto, public_key) POD_MAKE_COMPARABLE(crypto, secret_key) POD_MAKE_HASHABLE(crypto, key_image) POD_MAKE_COMPARABLE(crypto, signature) POD_MAKE_COMPARABLE(crypto, key_derivation) POD_MAKE_LESS_OPERATOR(crypto, hash) POD_MAKE_LESS_OPERATOR(crypto, key_image) -POP_GCC_WARNINGS \ No newline at end of file +POP_GCC_WARNINGS diff --git a/src/crypto/random.c b/src/crypto/random.c index a54bdf74..f204c163 100644 --- a/src/crypto/random.c +++ b/src/crypto/random.c @@ -1,3 +1,4 @@ +// Copyright (c) 2018-2025 Zano Project // Copyright (c) 2012-2013 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -5,9 +6,10 @@ #include #include #include +#include +#include #include "hash-ops.h" -//#include "initializer.h" #include "random.h" static_assert(RANDOM_STATE_SIZE >= HASH_DATA_AREA, "Invalid RANDOM_STATE_SIZE"); @@ -15,19 +17,29 @@ static_assert(RANDOM_STATE_SIZE >= HASH_DATA_AREA, "Invalid RANDOM_STATE_SIZE"); #if defined(_WIN32) #include -#include +#include -void generate_system_random_bytes(size_t n, void *result) { - HCRYPTPROV prov; -#define must_succeed(x) do if (!(x)) assert(0); while (0) - if(!CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) +// thread-safe version +bool generate_system_random_bytes(size_t n, void *result) +{ + if (n == 0) + return true; + + if (result == NULL) + return false; + + NTSTATUS status = BCryptGenRandom(NULL, (PUCHAR)result, (ULONG)n, BCRYPT_USE_SYSTEM_PREFERRED_RNG); + return BCRYPT_SUCCESS(status); +} + +void generate_system_random_bytes_or_die(size_t n, void *result) +{ + if (!generate_system_random_bytes(n, result)) { - int err = GetLastError(); - assert(0); + fprintf(stderr, "Error: generate_system_random_bytes failed and this is fatal\n\n"); + fflush(stderr); + _exit(EXIT_FAILURE); } - must_succeed(CryptGenRandom(prov, (DWORD)n, result)); - must_succeed(CryptReleaseContext(prov, 0)); -#undef must_succeed } #else @@ -40,28 +52,49 @@ void generate_system_random_bytes(size_t n, void *result) { #include #include -void generate_system_random_bytes(size_t n, void *result) { +bool generate_system_random_bytes(size_t n, void *result) +{ int fd; - if ((fd = open("/dev/urandom", O_RDONLY | O_NOCTTY | O_CLOEXEC)) < 0) { - exit(EXIT_FAILURE); - } - for (;;) { - ssize_t res = read(fd, result, n); - if ((size_t) res == n) { - break; - } - if (res < 0) { - if (errno != EINTR) { - exit(EXIT_FAILURE); + + fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); + if (fd < 0) + return false; + + size_t bytes_read = 0; + while (bytes_read < n) + { + ssize_t res = read(fd, (char*)result + bytes_read, n - bytes_read); + if (res < 0) + { + if (errno != EINTR) + { + close(fd); + return false; } - } else if (res == 0) { - exit(EXIT_FAILURE); - } else { - result = padd(result, (size_t) res); - n -= (size_t) res; + // EINTR - interrupted by signal, continue reading + } + else if (res == 0) + { + // EOF - should not happen with /dev/urandom + close(fd); + return false; + } + else + { + bytes_read += res; } } - if (close(fd) < 0) { + + close(fd); // don't check, 'cuz failing to close /dev/urandom is not truly fatal, the OS will clean up + return true; +} + +void generate_system_random_bytes_or_die(size_t n, void *result) +{ + if (!generate_system_random_bytes(n, result)) + { + fprintf(stderr, "FATAL: Failed to generate %zu random bytes: %s\n\n", n, strerror(errno)); + fflush(stderr); exit(EXIT_FAILURE); } } @@ -70,6 +103,8 @@ void generate_system_random_bytes(size_t n, void *result) { static union hash_state state; +static_assert(sizeof(union hash_state) == RANDOM_STATE_SIZE, "RANDOM_STATE_SIZE and hash_state size missmatch"); + #if !defined(NDEBUG) static volatile int curstate; /* To catch thread safety problems. */ #endif @@ -83,11 +118,10 @@ FINALIZER(deinit_random) { } */ -//INITIALIZER(init_random) { void init_random(void) { - generate_system_random_bytes(32, &state); - //REGISTER_FINA\LIZER(deinit_random); + generate_system_random_bytes_or_die(HASH_DATA_AREA, &state); + #if !defined(NDEBUG) assert(curstate == 0); curstate = 1; @@ -95,7 +129,7 @@ void init_random(void) } -void grant_random_initialize(void) +void grant_random_initialize_no_lock(void) { static bool initalized = false; if(!initalized) @@ -105,9 +139,9 @@ void grant_random_initialize(void) } } -void random_prng_initialize_with_seed(uint64_t seed) +void random_prng_initialize_with_seed_no_lock(uint64_t seed) { - grant_random_initialize(); + grant_random_initialize_no_lock(); #if !defined(NDEBUG) assert(curstate == 1); curstate = 3; @@ -122,9 +156,9 @@ void random_prng_initialize_with_seed(uint64_t seed) #endif } -void random_prng_get_state(void *state_buffer, const size_t buffer_size) +void random_prng_get_state_no_lock(void *state_buffer, const size_t buffer_size) { - grant_random_initialize(); + grant_random_initialize_no_lock(); #if !defined(NDEBUG) assert(curstate == 1); curstate = 4; @@ -139,9 +173,9 @@ void random_prng_get_state(void *state_buffer, const size_t buffer_size) #endif } -void random_prng_set_state(const void *state_buffer, const size_t buffer_size) +void random_prng_set_state_no_lock(const void *state_buffer, const size_t buffer_size) { - grant_random_initialize(); + grant_random_initialize_no_lock(); #if !defined(NDEBUG) assert(curstate == 1); curstate = 5; @@ -156,8 +190,9 @@ void random_prng_set_state(const void *state_buffer, const size_t buffer_size) #endif } -void generate_random_bytes(size_t n, void *result) { - grant_random_initialize(); +void generate_random_bytes_no_lock(size_t n, void *result) +{ + grant_random_initialize_no_lock(); #if !defined(NDEBUG) assert(curstate == 1); curstate = 2; diff --git a/src/crypto/random.h b/src/crypto/random.h index dbc79302..7db79b8b 100644 --- a/src/crypto/random.h +++ b/src/crypto/random.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2019 Zano Project +// Copyright (c) 2018-2025 Zano Project // Copyright (c) 2014-2018 The Boolberry developers // Copyright (c) 2012-2013 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying @@ -9,13 +9,8 @@ #include #include -// use the cryptographically secure Pseudo-Random Number Generator provided by the operating system -void generate_system_random_bytes(size_t n, void *result); - -void generate_random_bytes(size_t n, void *result); - -// checks if PRNG is initialized and initializes it if necessary -void grant_random_initialize(void); +// NOT thread-safe, use with caution +void generate_random_bytes_no_lock(size_t n, void *result); #define RANDOM_STATE_SIZE 200 @@ -24,14 +19,14 @@ void grant_random_initialize(void); // reinitializes PRNG with the given seed // !!!ATTENTION!!!! Improper use of this routine may lead to SECURITY BREACH! // Use with care and ONLY for tests or debug purposes! -void random_prng_initialize_with_seed(uint64_t seed); +void random_prng_initialize_with_seed_no_lock(uint64_t seed); // gets internal RPNG state (state_buffer should be 200 bytes long) -void random_prng_get_state(void *state_buffer, const size_t buffer_size); +void random_prng_get_state_no_lock(void *state_buffer, const size_t buffer_size); // sets internal RPNG state (state_buffer should be 200 bytes long) // !!!ATTENTION!!!! Improper use of this routine may lead to SECURITY BREACH! // Use with care and ONLY for tests or debug purposes! -void random_prng_set_state(const void *state_buffer, const size_t buffer_size); +void random_prng_set_state_no_lock(const void *state_buffer, const size_t buffer_size); #endif // #ifdef USE_INSECURE_RANDOM_RPNG_ROUTINES diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index 88082943..a6fb1142 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -74,8 +74,8 @@ using namespace currency; DISABLE_VS_WARNINGS(4267) -const command_line::arg_descriptor arg_db_cache_l1 ( "db-cache-l1", "Specify size of memory mapped db cache file"); -const command_line::arg_descriptor arg_db_cache_l2 ( "db-cache-l2", "Specify cached elements in db helpers"); +const command_line::arg_descriptor arg_db_cache_l1 ( "db-cache-l1", "Specify size of memory mapped db cache file"); +const command_line::arg_descriptor arg_db_cache_l2 ( "db-cache-l2", "Specify cached elements in db helpers"); //------------------------------------------------------------------ blockchain_storage::blockchain_storage(tx_memory_pool& tx_pool) :m_db(nullptr, m_rw_lock), @@ -7101,7 +7101,7 @@ bool blockchain_storage::handle_block_to_main_chain(const block& bl, const crypt if (!m_is_in_checkpoint_zone) { auto cleanup = [&](){ - bool add_res = m_tx_pool.add_tx(tx, tvc, true, true); + m_tx_pool.add_tx(tx, tvc, true, true); m_tx_pool.add_transaction_to_black_list(tx); purge_block_data_from_blockchain(bl, tx_processed_count); bvc.m_verification_failed = true; @@ -8102,7 +8102,7 @@ bool blockchain_storage::validate_alt_block_input(const transaction& input_tx, for (size_t pk_n = 0; pk_n < pub_keys.size(); ++pk_n) { - crypto::public_key& pk = pub_keys[pk_n]; + [[maybe_unused]] crypto::public_key& pk = pub_keys[pk_n]; crypto::hash tx_id = null_hash; uint64_t out_n = UINT64_MAX; auto &off = abs_key_offsets[pk_n]; diff --git a/src/currency_core/blockchain_storage.h b/src/currency_core/blockchain_storage.h index 65dbc07a..cccc8d83 100644 --- a/src/currency_core/blockchain_storage.h +++ b/src/currency_core/blockchain_storage.h @@ -50,7 +50,7 @@ MARK_AS_POD_C11(macro_alias_1); #undef LOG_DEFAULT_CHANNEL #define LOG_DEFAULT_CHANNEL "core" -extern const command_line::arg_descriptor arg_db_cache_l2; +extern const command_line::arg_descriptor arg_db_cache_l2; namespace currency { diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index 63f94212..c73660f6 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -1091,7 +1091,7 @@ namespace currency BOOST_SERIALIZE(attachment) BOOST_END_VERSION_UNDER(1) BOOST_SERIALIZE(proofs) - END_BOOST_SERIALIZATION() + END_BOOST_SERIALIZATION() }; diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index c8ad89d7..dbe4d82a 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -736,14 +736,15 @@ namespace currency //------------------------------------------------------------------ bool derive_ephemeral_key_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral) { + // TODO: re-implement this to avoid double Hs calculation -- sowle crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); - bool r = crypto::generate_key_derivation(tx_public_key, ack.view_secret_key, recv_derivation); + bool r = crypto::generate_key_derivation(tx_public_key, ack.view_secret_key, recv_derivation); // recv_derivation = 8 * ack.view_secret_key * tx_public_key CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.view_secret_key << ")"); - r = crypto::derive_public_key(recv_derivation, real_output_index, ack.account_address.spend_public_key, in_ephemeral.pub); + r = crypto::derive_public_key(recv_derivation, real_output_index, ack.account_address.spend_public_key, in_ephemeral.pub); // = Hs(recv_derivation, real_output_index) * G + spend_public_key CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to derive_public_key(" << recv_derivation << ", " << real_output_index << ", " << ack.account_address.spend_public_key << ")"); - crypto::derive_secret_key(recv_derivation, real_output_index, ack.spend_secret_key, in_ephemeral.sec); + crypto::derive_secret_key(recv_derivation, real_output_index, ack.spend_secret_key, in_ephemeral.sec); // = Hs(recv_derivation, real_output_index) + spend_secret_key return true; } //--------------------------------------------------------------- @@ -764,6 +765,8 @@ namespace currency //--------------------------------------------------------------- bool generate_key_image_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki) { + // h = Hs(8 * ack.view_secret_key * tx_public_key, real_output_index) + // ki = sec * Hp( pub ) = (h + spend_secret_key) * Hp( h * G + spend_public_key ) bool r = derive_ephemeral_key_helper(ack, tx_public_key, real_output_index, in_ephemeral); CHECK_AND_ASSERT_MES(r, false, "Failed to call derive_ephemeral_key_helper(...)"); @@ -1784,7 +1787,7 @@ namespace currency } //--------------------------------------------------------------- - void encrypt_attachments(transaction& tx, const account_keys& sender_keys, const account_public_address& destination_addr, const keypair& tx_random_key, crypto::key_derivation& derivation) + void encrypt_payload_items(transaction& tx, const account_keys& sender_keys, const account_public_address& destination_addr, const keypair& tx_random_key, crypto::key_derivation& derivation) { bool r = crypto::generate_key_derivation(destination_addr.view_public_key, tx_random_key.sec, derivation); CHECK_AND_ASSERT_MES(r, void(), "failed to generate_key_derivation"); @@ -1816,10 +1819,10 @@ namespace currency } } //--------------------------------------------------------------- - void encrypt_attachments(transaction& tx, const account_keys& sender_keys, const account_public_address& destination_addr, const keypair& tx_random_key) + void encrypt_payload_items(transaction& tx, const account_keys& sender_keys, const account_public_address& destination_addr, const keypair& tx_random_key) { crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); - return encrypt_attachments(tx, sender_keys, destination_addr, tx_random_key, derivation); + return encrypt_payload_items(tx, sender_keys, destination_addr, tx_random_key, derivation); } //--------------------------------------------------------------- void load_wallet_transfer_info_flags(tools::wallet_public::wallet_transfer_info& x) @@ -2582,7 +2585,7 @@ namespace currency //include offers if need tx.attachment = attachments; - encrypt_attachments(tx, sender_account_keys, crypt_destination_addr, gen_context.tx_key, result.derivation); + encrypt_payload_items(tx, sender_account_keys, crypt_destination_addr, gen_context.tx_key, result.derivation); } else { diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index 4d457150..4e67f9a0 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -396,8 +396,8 @@ namespace currency bool is_tx_spendtime_unlocked(uint64_t unlock_time, uint64_t current_blockchain_size, uint64_t current_time); crypto::key_derivation get_encryption_key_derivation(bool is_income, const transaction& tx, const account_keys& acc_keys); bool decrypt_payload_items(bool is_income, const transaction& tx, const account_keys& acc_keys, std::vector& decrypted_items); - void encrypt_attachments(transaction& tx, const account_keys& sender_keys, const account_public_address& destination_addr, const keypair& tx_random_key, crypto::key_derivation& derivation); - void encrypt_attachments(transaction& tx, const account_keys& sender_keys, const account_public_address& destination_addr, const keypair& tx_random_key); + void encrypt_payload_items(transaction& tx, const account_keys& sender_keys, const account_public_address& destination_addr, const keypair& tx_random_key, crypto::key_derivation& derivation); + void encrypt_payload_items(transaction& tx, const account_keys& sender_keys, const account_public_address& destination_addr, const keypair& tx_random_key); bool is_derivation_used_to_encrypt(const transaction& tx, const crypto::key_derivation& derivation); bool is_address_like_wrapped(const std::string& addr); void load_wallet_transfer_info_flags(tools::wallet_public::wallet_transfer_info& x); diff --git a/src/currency_core/currency_format_utils_transactions.h b/src/currency_core/currency_format_utils_transactions.h index 0ce53e85..6bb04b01 100644 --- a/src/currency_core/currency_format_utils_transactions.h +++ b/src/currency_core/currency_format_utils_transactions.h @@ -78,6 +78,7 @@ namespace currency FIELD(ms_keys_count) FIELD(separately_signed_tx_complete) FIELD(htlc_origin) + FIELD(asset_id) END_SERIALIZE() }; diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 5bb46f99..bcd40213 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 5bb46f99d5554d0c80752833a6bfd7ef4458174b +Subproject commit bcd4021364bbcd29a4208225de33a359ee35a447 diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index e9367faa..eda9ea37 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2019 Zano Project +// Copyright (c) 2014-2025 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying @@ -144,8 +144,7 @@ namespace nodetool HANDLE_INVOKE_T2(COMMAND_REQUEST_STAT_INFO, &node_server::handle_get_stat_info) HANDLE_INVOKE_T2(COMMAND_REQUEST_NETWORK_STATE, &node_server::handle_get_network_state) HANDLE_INVOKE_T2(COMMAND_REQUEST_PEER_ID, &node_server::handle_get_peer_id) - HANDLE_INVOKE_T2(COMMAND_REQUEST_LOG, &node_server::handle_request_log) - HANDLE_INVOKE_T2(COMMAND_SET_LOG_LEVEL, &node_server::handle_set_log_level) + HANDLE_INVOKE_T2(COMMAND_REQUEST_ANONYMIZED_PEERS, &node_server::handle_request_anonymized_peers) } #endif CHAIN_INVOKE_MAP_TO_OBJ_FORCE_CONTEXT(m_payload_handler, typename t_payload_net_handler::connection_context&) @@ -160,8 +159,7 @@ namespace nodetool int handle_get_stat_info(int command, typename COMMAND_REQUEST_STAT_INFO::request& arg, typename COMMAND_REQUEST_STAT_INFO::response& rsp, p2p_connection_context& context); int handle_get_network_state(int command, COMMAND_REQUEST_NETWORK_STATE::request& arg, COMMAND_REQUEST_NETWORK_STATE::response& rsp, p2p_connection_context& context); int handle_get_peer_id(int command, COMMAND_REQUEST_PEER_ID::request& arg, COMMAND_REQUEST_PEER_ID::response& rsp, p2p_connection_context& context); - int handle_request_log(int command, COMMAND_REQUEST_LOG::request& arg, COMMAND_REQUEST_LOG::response& rsp, p2p_connection_context& context); - int handle_set_log_level(int command, COMMAND_SET_LOG_LEVEL::request& arg, COMMAND_SET_LOG_LEVEL::response& rsp, p2p_connection_context& context); + int handle_request_anonymized_peers(int command, COMMAND_REQUEST_ANONYMIZED_PEERS::request& req, COMMAND_REQUEST_ANONYMIZED_PEERS::response& rsp, p2p_connection_context& context); private: #endif bool init_config(); diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index b5623ef3..9de58a86 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2019 Zano Project +// Copyright (c) 2014-2025 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying @@ -1321,7 +1321,7 @@ namespace nodetool } //----------------------------------------------------------------------------------- template - int node_server::handle_request_log(int command, COMMAND_REQUEST_LOG::request& req, COMMAND_REQUEST_LOG::response& rsp, p2p_connection_context& context) + int node_server::handle_request_anonymized_peers(int command, COMMAND_REQUEST_ANONYMIZED_PEERS::request& req, COMMAND_REQUEST_ANONYMIZED_PEERS::response& rsp, p2p_connection_context& context) { if (!check_trust(req.tr)) { @@ -1329,30 +1329,15 @@ namespace nodetool return 1; } - rsp.current_log_level = static_cast(log_space::get_set_log_detalisation_level()); - tools::get_log_chunk_gzipped(req.log_chunk_offset, req.log_chunk_size, rsp.log_chunk, rsp.error); - rsp.current_log_size = tools::get_log_file_size(); + m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cn_context) + { + auto& el = rsp.peers.emplace_back(); + el.inbound = cn_context.m_is_income; + el.time_started = cn_context.m_started; + return true; + }); - return 1; - } - //----------------------------------------------------------------------------------- - template - int node_server::handle_set_log_level(int command, COMMAND_SET_LOG_LEVEL::request& req, COMMAND_SET_LOG_LEVEL::response& rsp, p2p_connection_context& context) - { - if (!check_trust(req.tr)) - { - drop_connection(context); - return 1; - } - - rsp.old_log_level = static_cast(log_space::get_set_log_detalisation_level()); - log_space::get_set_log_detalisation_level(true, static_cast(req.new_log_level)); - rsp.current_log_level = static_cast(log_space::get_set_log_detalisation_level()); - - if (rsp.old_log_level != rsp.current_log_level) - { - LOG_PRINT_CC(context, "log level changed by debug command: " << rsp.old_log_level << " -> " << rsp.current_log_level, LOG_LEVEL_0); - } + std::shuffle(rsp.peers.begin(), rsp.peers.end(), crypto::uniform_random_bit_generator()); return 1; } diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index 8e3318f3..61d7db19 100644 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018 Zano Project +// Copyright (c) 2014-2025 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying @@ -425,65 +425,36 @@ namespace nodetool /************************************************************************/ /* */ /************************************************************************/ - struct COMMAND_REQUEST_LOG + struct COMMAND_REQUEST_ANONYMIZED_PEERS { - const static int ID = P2P_COMMANDS_POOL_BASE + 7; + const static int ID = P2P_COMMANDS_POOL_BASE + 100; struct request { proof_of_trust tr; - uint64_t log_chunk_offset; - uint64_t log_chunk_size; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tr) - KV_SERIALIZE(log_chunk_offset) - KV_SERIALIZE(log_chunk_size) END_KV_SERIALIZE_MAP() }; + struct anonymized_peer_info + { + uint64_t time_started; + bool inbound; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(time_started) + KV_SERIALIZE(inbound) + END_KV_SERIALIZE_MAP() + }; + struct response { - int64_t current_log_level; - uint64_t current_log_size; - std::string error; - std::string log_chunk; + std::vector peers; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(current_log_level) - KV_SERIALIZE(current_log_size) - KV_SERIALIZE(error) - KV_SERIALIZE(log_chunk) - END_KV_SERIALIZE_MAP() - }; - }; - - /************************************************************************/ - /* */ - /************************************************************************/ - struct COMMAND_SET_LOG_LEVEL - { - const static int ID = P2P_COMMANDS_POOL_BASE + 8; - - struct request - { - proof_of_trust tr; - int64_t new_log_level; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(tr) - KV_SERIALIZE(new_log_level) - END_KV_SERIALIZE_MAP() - }; - - struct response - { - int64_t old_log_level; - int64_t current_log_level; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(old_log_level) - KV_SERIALIZE(current_log_level) + KV_SERIALIZE(peers) END_KV_SERIALIZE_MAP() }; }; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 121586e6..0be19ed3 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -983,7 +983,7 @@ namespace currency if(!tvc.m_should_be_relayed) { - LOG_PRINT_L0("[on_send_raw_tx]: tx accepted, but not relayed"); + LOG_PRINT_L0("[on_send_raw_tx]: tx accepted, but not relayed, tx blob: " << req.tx_as_hex); res.status = "Not relayed"; return true; } diff --git a/src/simplewallet/password_container.cpp b/src/simplewallet/password_container.cpp index 6203eee9..ccba0c16 100644 --- a/src/simplewallet/password_container.cpp +++ b/src/simplewallet/password_container.cpp @@ -232,7 +232,10 @@ namespace tools else { m_password.push_back(ch); - std::cout << (char_to_replace_user_input != '\0' ? char_to_replace_user_input : ch); + std::cout.put(( + char_to_replace_user_input != '\0' ? + char_to_replace_user_input : static_cast(ch)) + ).flush(); } } diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 70028747..26c1a698 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2024 Zano Project +// Copyright (c) 2014-2025 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying @@ -96,22 +96,18 @@ namespace ph = boost::placeholders; } \ catch (const tools::error::transfer_error& e) \ { \ - LOG_ERROR("unknown transfer error: " << e.to_string()); \ - fail_msg_writer() << "unknown transfer error: " << e.what(); \ + fail_msg_writer() << "(transfer) " << e.what(); \ } \ catch (const tools::error::wallet_internal_error& e) \ { \ - LOG_ERROR("internal error: " << e.to_string()); \ - fail_msg_writer() << "internal error: " << e.what(); \ + fail_msg_writer() << "(internal) " << e.what(); \ } \ catch (const std::exception& e) \ { \ - LOG_ERROR("unexpected error: " << e.what()); \ - fail_msg_writer() << "unexpected error: " << e.what(); \ + fail_msg_writer() << "(unexpected) " << e.what(); \ } \ catch (...) \ { \ - LOG_ERROR("Unknown error"); \ fail_msg_writer() << "unknown error"; \ } \ @@ -145,6 +141,8 @@ namespace const command_line::arg_descriptor arg_voting_config_file("voting-config-file", "Set voting config instead of getting if from daemon", ""); const command_line::arg_descriptor arg_no_password_confirmations("no-password-confirmation", "Enable/Disable password confirmation for transactions", false); const command_line::arg_descriptor arg_seed_doctor("seed-doctor", "Experimental: if your seed is not working for recovery this is likely because you've made a mistake whene you were doing back up(typo, wrong words order, missing word). This experimental code will attempt to recover seed phrase from with few approaches."); + const command_line::arg_descriptor arg_no_whitelist("no-white-list", "Do not load white list from interned."); + const command_line::arg_descriptor arg_restore_ki_in_wo_wallet("restore-ki-in-wo-wallet", "Watch-only missing key images restoration. Please, DON'T use it unless you 100% sure of what are you doing.", ""); const command_line::arg_descriptor< std::vector > arg_command ("command", ""); @@ -301,7 +299,7 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("export_recent_transfers", boost::bind(&simple_wallet::export_recent_transfers, this, ph::_1), "list_recent_transfers_tx - Write recent transfer in json to wallet_recent_transfers.txt"); m_cmd_binder.set_handler("list_outputs", boost::bind(&simple_wallet::list_outputs, this, ph::_1), "list_outputs [spent|unspent] [ticker=ZANO] [unknown] - Lists all the outputs. The result may be filtered by spent status, asset ticker or unknown asset ids."); m_cmd_binder.set_handler("lo", boost::bind(&simple_wallet::list_outputs, this, ph::_1), "alias for list_outputs"); - m_cmd_binder.set_handler("dump_transfers", boost::bind(&simple_wallet::dump_trunsfers, this, ph::_1), "dump_transfers - Write transfers in json to dump_transfers.txt"); + m_cmd_binder.set_handler("dump_transfers", boost::bind(&simple_wallet::dump_transfers, this, ph::_1), "dump_transfers - Write transfers in json to dump_transfers.txt"); m_cmd_binder.set_handler("dump_keyimages", boost::bind(&simple_wallet::dump_key_images, this, ph::_1), "dump_keyimages - Write key_images in json to dump_key_images.txt"); m_cmd_binder.set_handler("payments", boost::bind(&simple_wallet::show_payments, this, ph::_1), "payments [ ... ] - Show payments , ... "); m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this,ph::_1), "Show blockchain height"); @@ -408,6 +406,16 @@ void process_wallet_command_line_params(const po::variables_map& vm, tools::wall wal.set_disable_tor_relay(true); } } + + if (command_line::has_arg(vm, arg_no_whitelist)) + { + wal.set_use_assets_whitelisting(!command_line::get_arg(vm, arg_no_whitelist)); + } + else + { + wal.set_use_assets_whitelisting(true); + } + if (command_line::has_arg(vm, arg_set_timeout)) { @@ -513,8 +521,10 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) else { bool r = open_wallet(m_wallet_file, pwd_container.password()); - CHECK_AND_ASSERT_MES(r, false, "could not open account"); + CHECK_AND_ASSERT_MES(r, false, "wallet could not be opened"); was_open = true; + if (!process_ki_restoration()) + return false; } process_wallet_command_line_params(vm, *m_wallet, false); @@ -554,6 +564,8 @@ void simple_wallet::handle_command_line(const boost::program_options::variables_ m_disable_tor = command_line::get_arg(vm, arg_disable_tor_relay); m_voting_config_file = command_line::get_arg(vm, arg_voting_config_file); m_no_password_confirmations = command_line::get_arg(vm, arg_no_password_confirmations); + m_no_whitelist = command_line::get_arg(vm, arg_no_whitelist); + m_restore_ki_in_wo_wallet = command_line::get_arg(vm, arg_restore_ki_in_wo_wallet); } //---------------------------------------------------------------------------------------------------- @@ -615,6 +627,7 @@ bool simple_wallet::new_wallet(const string &wallet_file, const std::string& pas m_wallet->generate(epee::string_encoding::utf8_to_wstring(m_wallet_file), password, create_auditable_wallet); message_writer(epee::log_space::console_color_white, true) << "Generated new " << (create_auditable_wallet ? "AUDITABLE" : "") << " wallet: " << m_wallet->get_account().get_public_address_str(); display_vote_info(*m_wallet); + preconfig_wallet_obj(); std::cout << "view key: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().view_secret_key) << std::endl << std::flush; if (m_wallet->is_auditable()) std::cout << "tracking seed: " << std::endl << m_wallet->get_account().get_tracking_seed() << std::endl << std::flush; @@ -663,6 +676,7 @@ bool simple_wallet::restore_wallet(const std::string& wallet_file, const std::st std::cout << "tracking seed: " << std::endl << m_wallet->get_account().get_tracking_seed() << std::endl << std::flush; } display_vote_info(*m_wallet); + preconfig_wallet_obj(); if (m_do_not_set_date) m_wallet->reset_creation_time(0); } @@ -689,7 +703,12 @@ bool simple_wallet::restore_wallet(const std::string& wallet_file, const std::st return true; } //---------------------------------------------------------------------------------------------------- - +void simple_wallet::preconfig_wallet_obj() +{ + if (m_no_whitelist) + m_wallet->set_use_assets_whitelisting(false); +} +// bool simple_wallet::open_wallet(const string &wallet_file, const std::string& password) { m_wallet_file = wallet_file; @@ -698,20 +717,25 @@ bool simple_wallet::open_wallet(const string &wallet_file, const std::string& pa if (!m_voting_config_file.empty()) m_wallet->set_votes_config_path(m_voting_config_file); + auto print_wallet_opened_msg = [&](){ + message_writer(epee::log_space::console_color_white, true) << "Opened" << (m_wallet->is_auditable() ? " auditable" : "") << (m_wallet->is_watch_only() ? " watch-only" : "") << " wallet: " << m_wallet->get_account().get_public_address_str(); + }; + while (true) { try { m_wallet->load(epee::string_encoding::utf8_to_wstring(m_wallet_file), password); - message_writer(epee::log_space::console_color_white, true) << "Opened" << (m_wallet->is_auditable() ? " auditable" : "") << (m_wallet->is_watch_only() ? " watch-only" : "") << " wallet: " << m_wallet->get_account().get_public_address_str(); + print_wallet_opened_msg(); + preconfig_wallet_obj(); display_vote_info(*m_wallet); - + break; } catch (const tools::error::wallet_load_notice_wallet_restored& /*e*/) { - message_writer(epee::log_space::console_color_white, true) << "Opened wallet: " << m_wallet->get_account().get_public_address_str(); + print_wallet_opened_msg(); message_writer(epee::log_space::console_color_red, true) << "NOTICE: Wallet file was damaged and restored."; break; } @@ -766,6 +790,27 @@ bool simple_wallet::save(const std::vector &args) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::process_ki_restoration() +{ + if (!m_restore_ki_in_wo_wallet.empty()) + { + std::wstring wo_filename = epee::string_encoding::utf8_to_wstring(m_restore_ki_in_wo_wallet); + CHECK_AND_ASSERT_THROW_MES(std::filesystem::exists(wo_filename), "cannot open " << m_restore_ki_in_wo_wallet); + + tools::password_container wo_password; + if (!wo_password.read_password("Enter password for wallet " + m_restore_ki_in_wo_wallet + " :")) + return false; + + m_wallet->restore_key_images_in_wo_wallet(wo_filename, wo_password.password()); + + success_msg_writer() << "Missing key images have been successfully repared in " << m_restore_ki_in_wo_wallet << ENDL; + + return false; // means the wallet processing should stop now + } + + return true; // means the wallet can load and work further normally +} +//---------------------------------------------------------------------------------------------------- #ifdef CPU_MINING_ENABLED bool simple_wallet::start_mining(const std::vector& args) { @@ -898,7 +943,7 @@ void simple_wallet::on_transfer2(const tools::wallet_public::wallet_transfer_inf ", tx " << wti.tx_hash; for (const auto& st : wti.subtransfers) { - epee::log_space::console_colors color = st.is_income ? epee::log_space::console_color_green : epee::log_space::console_color_magenta; + [[maybe_unused]] epee::log_space::console_colors color = st.is_income ? epee::log_space::console_color_green : epee::log_space::console_color_magenta; uint64_t decimal_points = CURRENCY_DISPLAY_DECIMAL_POINT; std::string token_info = get_token_info_string(st.asset_id, decimal_points); @@ -1131,7 +1176,7 @@ std::string wti_to_text_line(const tools::wallet_public::wallet_transfer_info& w //---------------------------------------------------------------------------------------------------- bool simple_wallet::export_recent_transfers(const std::vector& args) { - bool export_to_json = true; + [[maybe_unused]] bool export_to_json = true; bool ignore_pos = false; if (args.size() > 1) { @@ -1167,12 +1212,12 @@ bool simple_wallet::export_recent_transfers(const std::vector& args return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::dump_trunsfers(const std::vector& args) +bool simple_wallet::dump_transfers(const std::vector& args) { stringstream ss; success_msg_writer() << "Generating text...."; - m_wallet->dump_trunsfers(ss); + m_wallet->dump_transfers(ss); success_msg_writer() << "Storing text to dump_transfers.txt...."; file_io_utils::save_string_to_file(log_space::log_singletone::get_default_log_folder() + "/dump_transfers.txt", ss.str()); success_msg_writer() << "Done...."; @@ -1850,6 +1895,12 @@ bool simple_wallet::print_address(const std::vector &args/* = std:: //---------------------------------------------------------------------------------------------------- bool simple_wallet::show_seed(const std::vector &args) { + if (m_wallet->is_watch_only()) + { + fail_msg_writer() << "watch-only wallet doesn't have the full set of keys, hence no seed phrase can be generated"; + return false; + } + CONFIRM_WITH_PASSWORD(); success_msg_writer() << "Please enter a password to secure this seed. Securing your seed is HIGHLY recommended. Leave password blank to stay unsecured."; success_msg_writer(true) << "Remember, restoring a wallet from Secured Seed can only be done if you know its password."; @@ -2037,12 +2088,10 @@ bool simple_wallet::save_watch_only(const std::vector &args) } catch (const std::exception& e) { - LOG_ERROR("unexpected error: " << e.what()); - fail_msg_writer() << "unexpected error: " << e.what(); + fail_msg_writer() << e.what(); } catch (...) { - LOG_ERROR("Unknown error"); fail_msg_writer() << "unknown error"; } return true; @@ -2106,12 +2155,10 @@ bool simple_wallet::sign_transfer(const std::vector &args) } catch (const std::exception& e) { - LOG_ERROR("unexpected error: " << e.what()); - fail_msg_writer() << "unexpected error: " << e.what(); + fail_msg_writer() << e.what(); } catch (...) { - LOG_ERROR("Unknown error"); fail_msg_writer() << "unknown error"; } return true; @@ -2132,12 +2179,10 @@ bool simple_wallet::submit_transfer(const std::vector &args) } catch (const std::exception& e) { - LOG_ERROR("unexpected error: " << e.what()); - fail_msg_writer() << "unexpected error: " << e.what(); + fail_msg_writer() << e.what(); } catch (...) { - LOG_ERROR("Unknown error"); fail_msg_writer() << "unknown error"; } return true; @@ -2745,7 +2790,6 @@ bool simple_wallet::sweep_bare_outs(const std::vector &args) { CONFIRM_WITH_PASSWORD(); SIMPLE_WALLET_BEGIN_TRY_ENTRY(); - bool r = false; if (args.size() > 1) { @@ -3097,7 +3141,7 @@ int seed_doctor() } bool pass_protected = false; - bool success = account_base::is_seed_password_protected(seed, pass_protected); + account_base::is_seed_password_protected(seed, pass_protected); success_msg_writer() << "SECURED_SEED: " << (pass_protected ? "true" : "false"); if (pass_protected) @@ -3203,7 +3247,7 @@ int seed_doctor() success_msg_writer() << "No address and no checksum, recovery is limited only to date reset"; std::string result = boost::algorithm::join(words, " "); account_base acc; - bool r = acc.restore_from_seed_phrase(result, passphrase); + acc.restore_from_seed_phrase(result, passphrase); success_msg_writer() << "Potential seed candidate:\n" << result << "\nAddress: " << acc.get_public_address_str(); return EXIT_FAILURE; } @@ -3297,6 +3341,8 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, command_line::arg_generate_rpc_autodoc); command_line::add_arg(desc_params, arg_seed_doctor); command_line::add_arg(desc_params, arg_derive_custom_seed); + command_line::add_arg(desc_params, arg_no_whitelist); + command_line::add_arg(desc_params, arg_restore_ki_in_wo_wallet); tools::wallet_rpc_server::init_options(desc_params); @@ -3323,7 +3369,7 @@ int main(int argc, char* argv[]) } else if (command_line::get_arg(vm, command_line::arg_version)) { - success_msg_writer() << CURRENCY_NAME << " wallet v" << PROJECT_VERSION_LONG; + success_msg_writer() << CURRENCY_NAME << " simplewallet v" << PROJECT_VERSION_LONG; exit_requested = true; return true; } @@ -3349,6 +3395,7 @@ int main(int argc, char* argv[]) std::string log_dir; log_dir = log_file_path.has_parent_path() ? log_file_path.parent_path().string() : log_space::log_singletone::get_default_log_folder(); log_space::log_singletone::add_logger(LOGGER_FILE, log_file_path.filename().string().c_str(), log_dir.c_str(), LOG_LEVEL_4); + LOG_PRINT_L0(ENDL << ENDL); message_writer(epee::log_space::console_color_white, true, std::string(), LOG_LEVEL_0) << CURRENCY_NAME << " simplewallet v" << PROJECT_VERSION_LONG; if (command_line::has_arg(vm, command_line::arg_log_level)) @@ -3488,7 +3535,7 @@ int main(int argc, char* argv[]) if (command_line::get_arg(vm, arg_generate_new_wallet).size() || command_line::get_arg(vm, arg_generate_new_auditable_wallet).size()) return EXIT_FAILURE; - wal.set_use_assets_whitelisting(true); + wal.callback(callback); if (!offline_mode) diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 60c5217b..13ef637f 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -58,7 +58,7 @@ namespace currency bool show_balance(const std::vector &args = std::vector()); bool list_recent_transfers(const std::vector& args); bool export_recent_transfers(const std::vector& args); - bool dump_trunsfers(const std::vector& args); + bool dump_transfers(const std::vector& args); bool dump_key_images(const std::vector& args); bool show_incoming_transfers(const std::vector &args); bool show_staking_history(const std::vector& args); @@ -182,6 +182,9 @@ namespace currency }; private: + void preconfig_wallet_obj(); + bool process_ki_restoration(); + std::string m_wallet_file; std::string m_generate_new; std::string m_generate_new_aw; @@ -198,6 +201,8 @@ namespace currency std::string m_restore_wallet; std::string m_voting_config_file; bool m_no_password_confirmations = false; + bool m_no_whitelist = false; + std::string m_restore_ki_in_wo_wallet; crypto::hash m_password_hash; uint64_t m_password_salt; diff --git a/src/version.h.in b/src/version.h.in index 8d84a038..7da710ba 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -5,9 +5,9 @@ #define PROJECT_MAJOR_VERSION "2" #define PROJECT_MINOR_VERSION "1" -#define PROJECT_REVISION "7" +#define PROJECT_REVISION "8" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 424 +#define PROJECT_VERSION_BUILD_NO 427 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" diff --git a/src/wallet/plain_wallet_api.cpp b/src/wallet/plain_wallet_api.cpp index d242c760..eceb536e 100644 --- a/src/wallet/plain_wallet_api.cpp +++ b/src/wallet/plain_wallet_api.cpp @@ -604,7 +604,9 @@ namespace plain_wallet { PLAIN_WALLET_BEGIN_TRY_ENTRY(); GET_INSTANCE_PTR(inst_ptr); - return inst_ptr->gwm.invoke(h, params); + std::string res = inst_ptr->gwm.invoke(h, params); + tools::sanitize_utf8(res); + return res; PLAIN_WALLET_CATCH(); } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 258f1418..42f7ac0c 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -92,6 +92,11 @@ namespace tools m_core_runtime_config = currency::get_default_core_runtime_config(); } //--------------------------------------------------------------- + wallet2::~wallet2() + { + // do nothing + } + //--------------------------------------------------------------- uint64_t wallet2::get_max_unlock_time_from_receive_indices(const currency::transaction& tx, const wallet_public::employed_tx_entries& td) { uint64_t max_unlock_time = 0; @@ -717,7 +722,7 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t { // normal wallet, calculate and store key images for own outs currency::keypair in_ephemeral = AUTO_VAL_INIT(in_ephemeral); - currency::generate_key_image_helper(m_account.get_keys(), ptc.tx_pub_key, o, in_ephemeral, ki); + currency::generate_key_image_helper(m_account.get_keys(), ptc.tx_pub_key, /* output index */ o, in_ephemeral, ki); WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(in_ephemeral.pub == out_key, "key_image generated ephemeral public key that does not match with output_key"); } @@ -4218,29 +4223,25 @@ void wallet2::sign_transfer(const std::string& tx_sources_blob, std::string& sig // assumed to be called from normal, non-watch-only wallet THROW_IF_FALSE_WALLET_EX(!m_watch_only, error::wallet_common_error, "watch-only wallet is unable to sign transfers, you need to use normal wallet for that"); - // decrypt the blob - std::string decrypted_src_blob = crypto::chacha_crypt(tx_sources_blob, m_account.get_keys().view_secret_key); + // decrypt the blob + std::string decrypted_src_blob = crypto::chacha_crypt(tx_sources_blob, m_account.get_keys().view_secret_key); // deserialize args - currency::finalized_tx ft = AUTO_VAL_INIT(ft); + currency::finalized_tx ft{}; bool r = t_unserializable_object_from_blob(ft.ftp, decrypted_src_blob); THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, "Failed to decrypt tx sources blob"); // make sure unsigned tx was created with the same keys THROW_IF_FALSE_WALLET_EX(ft.ftp.spend_pub_key == m_account.get_keys().account_address.spend_public_key, error::wallet_common_error, "The was created in a different wallet, keys missmatch"); - finalize_transaction(ft.ftp, ft.tx, ft.one_time_key, false); + finalize_transaction(ft.ftp, ft, false, true); + + WLT_LOG_L0("sign_transfer: tx " << ft.tx_id << " has been successfully signed"); // calculate key images for each change output - crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX( - crypto::generate_key_derivation( - m_account.get_keys().account_address.view_public_key, - ft.one_time_key, - derivation), - "internal error: sign_transfer: failed to generate key derivation(" - << m_account.get_keys().account_address.view_public_key - << ", view secret key: " << ft.one_time_key << ")"); + crypto::key_derivation derivation{}; + r = crypto::generate_key_derivation(m_account.get_keys().account_address.view_public_key, ft.one_time_key, derivation); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "sign_transfer: generate_key_derivation failed, tx: " << ft.tx_id); for (size_t i = 0; i < ft.tx.vout.size(); ++i) { @@ -4250,7 +4251,7 @@ void wallet2::sign_transfer(const std::string& tx_sources_blob, std::string& sig crypto::public_key ephemeral_pub{}; if (!crypto::derive_public_key(derivation, i, m_account.get_keys().account_address.spend_public_key, ephemeral_pub)) { - WLT_LOG_ERROR("derive_public_key failed for tx " << get_transaction_hash(ft.tx) << ", out # " << i); + WLT_LOG_ERROR("derive_public_key failed for tx " << ft.tx_id << ", out # " << i); } if (out_pk == ephemeral_pub) @@ -4263,6 +4264,7 @@ void wallet2::sign_transfer(const std::string& tx_sources_blob, std::string& sig crypto::generate_key_image(ephemeral_pub, ephemeral_sec, ki); ft.outs_key_images.push_back(make_serializable_pair(static_cast(i), ki)); + WLT_LOG_L1("sign_transfer: tx " << ft.tx_id << ", out index: " << i << ", ki: " << ki); } } @@ -4391,6 +4393,9 @@ bool wallet2::attach_asset_descriptor(const wallet_public::COMMAND_ATTACH_ASSET_ //---------------------------------------------------------------------------------------------------- void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::transaction& tx) { + // assumed to be called from watch-only wallet + THROW_IF_FALSE_WALLET_EX(m_watch_only, error::wallet_common_error, "submit_transfer should be called in watch-only wallet only"); + // decrypt sources std::string decrypted_src_blob = crypto::chacha_crypt(signed_tx_blob, m_account.get_keys().view_secret_key); @@ -4401,9 +4406,36 @@ void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::trans tx = ft.tx; crypto::hash tx_hash = get_transaction_hash(tx); - // foolproof + // foolproof check THROW_IF_FALSE_WALLET_CMN_ERR_EX(ft.ftp.spend_pub_key == m_account.get_keys().account_address.spend_public_key, "The given tx was created in a different wallet, keys missmatch, tx hash: " << tx_hash); + // prepare and check data for watch-only outkey2ki before sending the tx + std::vector> pk_ki_to_be_added; + std::vector> tri_ki_to_be_added; + if (m_watch_only) + { + for (auto& p : ft.outs_key_images) + { + THROW_IF_FALSE_WALLET_INT_ERR_EX(p.first < tx.vout.size(), "outs_key_images has invalid out index: " << p.first << ", tx.vout.size() = " << tx.vout.size()); + std::list stub{}; + const crypto::public_key& pk = out_get_pub_key(tx.vout[p.first], stub); + pk_ki_to_be_added.push_back(std::make_pair(pk, p.second)); + } + + THROW_IF_FALSE_WALLET_INT_ERR_EX(tx.vin.size() == ft.ftp.sources.size(), "tx.vin and ft.ftp.sources sizes missmatch"); + for (size_t i = 0; i < tx.vin.size(); ++i) + { + const crypto::key_image& ki = get_key_image_from_txin_v(tx.vin[i]); + const auto& src = ft.ftp.sources[i]; + THROW_IF_FALSE_WALLET_INT_ERR_EX(src.real_output < src.outputs.size(), "src.real_output is out of bounds: " << src.real_output); + const crypto::public_key& out_key = src.outputs[src.real_output].stealth_address; + tri_ki_to_be_added.push_back(std::make_pair(src.transfer_index, ki)); + pk_ki_to_be_added.push_back(std::make_pair(out_key, ki)); + } + } + + // SEND the transaction + try { send_transaction_to_network(tx); @@ -4419,38 +4451,16 @@ void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::trans add_sent_tx_detailed_info(tx, ft.ftp.attachments, ft.ftp.prepared_destinations, ft.ftp.selected_transfers); m_tx_keys.insert(std::make_pair(tx_hash, ft.one_time_key)); + // populate and store key images from own outputs, because otherwise a watch-only wallet cannot calculate it if (m_watch_only) { - std::vector> pk_ki_to_be_added; - std::vector> tri_ki_to_be_added; - - for (auto& p : ft.outs_key_images) - { - THROW_IF_FALSE_WALLET_INT_ERR_EX(p.first < tx.vout.size(), "outs_key_images has invalid out index: " << p.first << ", tx.vout.size() = " << tx.vout.size()); - std::list stub{}; - const crypto::public_key& pk = out_get_pub_key(tx.vout[p.first], stub); - pk_ki_to_be_added.push_back(std::make_pair(pk, p.second)); - } - - THROW_IF_FALSE_WALLET_INT_ERR_EX(tx.vin.size() == ft.ftp.sources.size(), "tx.vin and ft.ftp.sources sizes missmatch"); - for (size_t i = 0; i < tx.vin.size(); ++i) - { - const crypto::key_image& ki = get_key_image_from_txin_v(tx.vin[i]); - - const auto& src = ft.ftp.sources[i]; - THROW_IF_FALSE_WALLET_INT_ERR_EX(src.real_output < src.outputs.size(), "src.real_output is out of bounds: " << src.real_output); - const crypto::public_key& out_key = src.outputs[src.real_output].stealth_address; - - tri_ki_to_be_added.push_back(std::make_pair(src.transfer_index, ki)); - pk_ki_to_be_added.push_back(std::make_pair(out_key, ki)); - } - for (auto& p : pk_ki_to_be_added) { auto it = m_pending_key_images.find(p.first); if (it != m_pending_key_images.end()) { - LOG_PRINT_YELLOW("warning: for tx " << tx_hash << " out pub key " << p.first << " already exist in m_pending_key_images, ki: " << it->second << ", proposed new ki: " << p.second, LOG_LEVEL_0); + if (it->second != p.second) + LOG_PRINT_YELLOW("warning: for tx " << tx_hash << " out pub key " << p.first << " already exist in m_pending_key_images, ki: " << it->second << ", proposed new ki: " << p.second, LOG_LEVEL_0); } else { @@ -5122,7 +5132,11 @@ bool wallet2::reset_history() std::wstring file_path = m_wallet_file; account_base acc_tmp = m_account; auto tx_keys = m_tx_keys; + auto pending_key_images = m_pending_key_images; + crypto::hash genesis_id = m_chain.get_genesis(); clear(); + m_chain.set_genesis(genesis_id); + m_pending_key_images = pending_key_images; m_tx_keys = tx_keys; m_account = acc_tmp; m_password = pass; @@ -5812,9 +5826,9 @@ struct local_transfers_struct END_KV_SERIALIZE_MAP() }; -void wallet2::dump_trunsfers(std::stringstream& ss, bool verbose, const crypto::public_key& asset_id) const +void wallet2::dump_transfers(std::stringstream& ss, bool verbose, const crypto::public_key& asset_id_to_filter) const { - bool filter_by_asset_id = asset_id != currency::null_pkey; + bool filter_by_asset_id = asset_id_to_filter != currency::null_pkey; if (verbose) { @@ -5824,7 +5838,7 @@ void wallet2::dump_trunsfers(std::stringstream& ss, bool verbose, const crypto:: { uint64_t i = tr.first; const transfer_details& td = tr.second; - if (filter_by_asset_id && td.get_asset_id() != asset_id) + if (filter_by_asset_id && td.get_asset_id() != asset_id_to_filter) continue; ss << "{ \"i\": " << i << "," << ENDL; ss << "\"entry\": " << epee::serialization::store_t_to_json(td) << "}," << ENDL; @@ -5833,16 +5847,22 @@ void wallet2::dump_trunsfers(std::stringstream& ss, bool verbose, const crypto:: else { boost::io::ios_flags_saver ifs(ss); - ss << "index amount spent_h g_index block block_ts flg tx out# key image" << ENDL; + ss << "index amount spent_h g_index block block_ts flg tx out# asset id" << ENDL; for (const auto& tr : m_transfers) { uint64_t i = tr.first; const transfer_details& td = tr.second; - if (filter_by_asset_id && td.get_asset_id() != asset_id) + const crypto::public_key asset_id = td.get_asset_id(); + if (filter_by_asset_id && asset_id != asset_id_to_filter) continue; + + uint32_t asset_flags = 0; + currency::asset_descriptor_base asset_info{}; + get_asset_info(asset_id, asset_info, asset_flags); + ss << std::right << std::setw(5) << i << " " << - std::setw(21) << print_money(td.amount()) << " " << + std::setw(21) << print_money(td.amount(), asset_info.decimal_point) << " " << std::setw(7) << td.m_spent_height << " " << std::setw(7) << td.m_global_output_index << " " << std::setw(6) << td.m_ptx_wallet_info->m_block_height << " " << @@ -5850,15 +5870,15 @@ void wallet2::dump_trunsfers(std::stringstream& ss, bool verbose, const crypto:: std::setw(4) << td.m_flags << " " << get_transaction_hash(td.m_ptx_wallet_info->m_tx) << " " << std::setw(4) << td.m_internal_output_index << " " << - td.m_key_image << ENDL; + (asset_id == native_coin_asset_id ? std::string() : crypto::pod_to_hex(asset_id)) << ENDL; } } } //---------------------------------------------------------------------------------------------------- -std::string wallet2::dump_trunsfers(bool verbose, const crypto::public_key& asset_id) const +std::string wallet2::dump_transfers(bool verbose, const crypto::public_key& asset_id) const { std::stringstream ss; - dump_trunsfers(ss, verbose, asset_id); + dump_transfers(ss, verbose, asset_id); return ss.str(); } //---------------------------------------------------------------------------------------------------- @@ -6617,8 +6637,8 @@ bool wallet2::prepare_tx_sources(size_t fake_outputs_count_, bool use_all_decoys if (true) { size_t fake_outputs_count = fake_outputs_count_; - uint64_t zarcanum_start_from = m_core_runtime_config.hard_forks.m_height_the_hardfork_n_active_after[ZANO_HARDFORK_04_ZARCANUM]; - uint64_t current_size = m_chain.get_blockchain_current_size(); + [[maybe_unused]] uint64_t zarcanum_start_from = m_core_runtime_config.hard_forks.m_height_the_hardfork_n_active_after[ZANO_HARDFORK_04_ZARCANUM]; + [[maybe_unused]] uint64_t current_size = m_chain.get_blockchain_current_size(); bool need_to_request = fake_outputs_count != 0; COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3::request req = AUTO_VAL_INIT(req); @@ -7630,6 +7650,122 @@ bool wallet2::is_need_to_split_outputs() return !is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM); } //---------------------------------------------------------------------------------------------------- +void wallet2::restore_key_images_in_wo_wallet(const std::wstring& filename, const std::string& password) const +{ + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!m_watch_only, "restore_key_images_in_wo_wallet can only be used in non watch-only wallet"); + bool r = false; + + // load the given watch-only wallet + wallet2 wo; + wo.load(filename, password); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(wo.is_watch_only(), epee::string_encoding::wstring_to_utf8(filename) << " is not a watch-only wallet"); + if (m_account.get_keys().view_secret_key != wo.get_account().get_keys().view_secret_key || + m_account.get_public_address() != wo.get_account().get_public_address()) + { + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(false, epee::string_encoding::wstring_to_utf8(filename) << " has keys that differ from this wallet's keys; wrong wallet?"); + } + + // + // 1. Find missing key images and calculate them using secret spend key. Populate missing_ki_items container. + // + struct missing_ki_item + { + crypto::public_key tx_pub_key; + crypto::public_key out_pub_key; + uint64_t output_index; + uint64_t transfer_index; + crypto::key_image ki; + }; + + std::set transfer_indices_to_include; + for(auto el : wo.m_pending_key_images) + { + const crypto::key_image& ki = el.second; + auto it = wo.m_key_images.find(ki); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != wo.m_key_images.end(), "restore_key_images_in_wo_wallet: m_key_images inconsistency, ki: " << ki); + size_t transfer_index = it->second; + transfer_indices_to_include.insert(transfer_index); + WLT_LOG_L1("restore_key_images_in_wo_wallet: transfer " << transfer_index << " is in m_pending_key_images, included"); + } + + for(auto el : wo.m_transfers) + { + size_t transfer_index = el.first; + if (el.second.m_key_image == null_ki) + { + transfer_indices_to_include.insert(transfer_index); + WLT_LOG_L1("restore_key_images_in_wo_wallet: ki is null for ti " << transfer_index << ", included"); + } + } + + // now in transfer_indices_to_include we have ordered and unique list of transfer indices + std::vector missing_ki_items; + std::set pk_uniqueness_set; + for(size_t transfer_index : transfer_indices_to_include) + { + const auto& td = wo.m_transfers.at(transfer_index); + auto& item = missing_ki_items.emplace_back(); + item.output_index = td.m_internal_output_index; + crypto::public_key out_pub_key{}; + r = get_out_pub_key_from_tx_out_v(td.output(), out_pub_key); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "restore_key_images_in_wo_wallet failed for ti: " << transfer_index); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(pk_uniqueness_set.insert(out_pub_key).second, "restore_key_images_in_wo_wallet: out pub key in not unique: " << out_pub_key << ", ti: " << transfer_index); + item.out_pub_key = out_pub_key; + item.transfer_index = transfer_index; + item.tx_pub_key = get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx); + WLT_LOG_L0("restore_key_images_in_wo_wallet: including: " << item.out_pub_key << ", " << transfer_index); + + // calculate key image + keypair ephemeral{}; + generate_key_image_helper(m_account.get_keys(), item.tx_pub_key, item.output_index, ephemeral, item.ki); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(ephemeral.pub == item.out_pub_key, "restore_key_images_in_wo_wallet: out pub key missmatch, ti: " << transfer_index); + }; + + // + // 2. Actually restore key images in the 'wo' object. + // + r = wo.m_pending_key_images_file_container.clear(); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "restore_key_images_in_wo_wallet: pending ki container clearing failed"); + wo.m_pending_key_images.clear(); + + for(size_t i = 0; i < missing_ki_items.size(); ++i) + { + const auto& item = missing_ki_items[i]; + auto& td = wo.m_transfers[item.transfer_index]; // item.transfer_index validity was checked above + + td.m_key_image = item.ki; // TODO: it's unclear whether we need to update m_transfers[].m_key_image since later I decided to clear history to trigger resync later. Probably, no. -- sowle + r = wo.m_pending_key_images.insert(std::make_pair(item.out_pub_key, item.ki)).second; + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "restore_key_images_in_wo_wallet: insert failed, out_pub_key: " << item.out_pub_key << ", i: " << i); + wo.m_pending_key_images_file_container.push_back(out_key_to_ki{item.out_pub_key, item.ki}); + LOG_PRINT_L0("restore_key_images_in_wo_wallet: added #" << i << " ti: " << item.transfer_index << ", pk: " << item.out_pub_key << ", ki: " << item.ki); + } + + wo.reset_history(); + wo.store(); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::clear_utxo_cold_sig_reservation(std::vector& affected_transfer_ids) +{ + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(m_watch_only, "clear_utxo_cold_sig_reservation can only be used in watch-only wallet"); + affected_transfer_ids.clear(); + + for(auto& [tid, td] : m_transfers) + { + if (!td.is_spent() && (td.m_flags & WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION) != 0) + { + affected_transfer_ids.push_back(tid); + crypto::public_key pk{}; + get_out_pub_key_from_tx_out_v(td.output(), pk); + WLT_LOG_L0("clear_utxo_cold_sig_reservation: tid: " << tid << ", pk: " << pk << ", amount: " << td.amount() << + (!td.is_native_coin() ? std::string(", aid: ") + crypto::pod_to_hex(td.get_asset_id()) : std::string()) << + (td.m_key_image != null_ki ? std::string(", ki: ") + crypto::pod_to_hex(td.m_key_image) : std::string())); + } + } + + clear_transfers_from_flag(affected_transfer_ids, WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION, "clear_utxo_cold_sig_reservation"); + m_found_free_amounts.clear(); +} +//---------------------------------------------------------------------------------------------------- void wallet2::prepare_tx_destinations(const assets_selection_context& needed_money_map, detail::split_strategy_id_t destination_split_strategy_id, const tx_dust_policy& dust_policy, @@ -8002,6 +8138,20 @@ void wallet2::check_and_throw_if_self_directed_tx_with_payment_id_requested(cons WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!has_payment_id, "sending funds to yourself with payment id is not allowed"); } //---------------------------------------------------------------------------------------------------- +void wallet2::check_and_throw_if_smth_not_good_with_comment_or_payment_id(const construct_tx_param& ctp) +{ + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!have_type_in_variant_container(ctp.attachments), "tx_comment is not allowed to be in attachments"); + + if (ctp.dsts.size() > 1 && have_type_in_variant_container(ctp.extra)) + { + const auto& first_destination = ctp.dsts.front(); + for(size_t i = 1; i < ctp.dsts.size(); ++i) + { + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(ctp.dsts[i].addr == first_destination.addr, "currently tx_comment cannot be used with multi-destination transfer"); + } + } +} +//---------------------------------------------------------------------------------------------------- void wallet2::transfer(construct_tx_param& ctp, currency::transaction& tx, bool send_to_network, @@ -8020,6 +8170,7 @@ void wallet2::transfer(construct_tx_param& ctp, WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!is_auditable() || !is_watch_only(), "You can't initiate coins transfer using an auditable watch-only wallet."); // btw, watch-only wallets can call transfer() within cold-signing process check_and_throw_if_self_directed_tx_with_payment_id_requested(ctp); + check_and_throw_if_smth_not_good_with_comment_or_payment_id(ctp); bool asset_operation_requested = count_type_in_variant_container(ctp.extra) != 0; bool dont_have_zero_asset_ids_in_destinations = std::count_if(ctp.dsts.begin(), ctp.dsts.end(), [](const tx_destination_entry& de) { return de.asset_id == null_pkey; }) == 0; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 717adf7d..ef2c3a39 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -262,7 +262,7 @@ namespace tools wallet2(const wallet2&) = delete; public: wallet2(); - virtual ~wallet2() {} + virtual ~wallet2(); static std::string transfer_flags_to_str(uint32_t flags); @@ -624,6 +624,9 @@ namespace tools void submit_transfer_files(const std::string& signed_tx_file, currency::transaction& tx); void submit_externally_signed_asset_tx(const currency::finalized_tx& ft, const crypto::generic_schnorr_sig_s& gss_sig, bool unlock_transfers_on_fail, currency::transaction& result_tx, bool& transfers_unlocked); void submit_externally_signed_asset_tx(const currency::finalized_tx& ft, const crypto::eth_signature& eth_sig, bool unlock_transfers_on_fail, currency::transaction& result_tx, bool& transfers_unlocked); + + void restore_key_images_in_wo_wallet(const std::wstring& filename, const std::string& password) const; + void clear_utxo_cold_sig_reservation(std::vector& affected_transfer_ids); void sweep_below(size_t fake_outs_count, const currency::account_public_address& destination_addr, uint64_t threshold_amount, const currency::payment_id_t& payment_id, uint64_t fee, size_t& outs_total, uint64_t& amount_total, size_t& outs_swept, uint64_t& amount_swept, currency::transaction* p_result_tx = nullptr, std::string* p_filename_or_unsigned_tx_blob_str = nullptr); @@ -690,8 +693,8 @@ namespace tools void scan_tx_to_key_inputs(std::vector& found_transfers, const currency::transaction& tx); // asset_id = null_pkey means no filtering by asset id - void dump_trunsfers(std::stringstream& ss, bool verbose = true, const crypto::public_key& asset_id = currency::null_pkey) const; - std::string dump_trunsfers(bool verbose = false, const crypto::public_key& asset_id = currency::null_pkey) const; + void dump_transfers(std::stringstream& ss, bool verbose = true, const crypto::public_key& asset_id = currency::null_pkey) const; + std::string dump_transfers(bool verbose = false, const crypto::public_key& asset_id = currency::null_pkey) const; void dump_key_images(std::stringstream& ss); void get_multisig_transfers(multisig_transfer_container& ms_transfers); const multisig_transfer_container& get_multisig_transfers() const { return m_multisig_transfers; } @@ -909,6 +912,7 @@ private: bool generate_utxo_defragmentation_transaction_if_needed(currency::transaction& tx); bool store_unsigned_tx_to_file_and_reserve_transfers(const currency::finalize_tx_param& ftp, const std::string& filename, std::string* p_unsigned_tx_blob_str = nullptr); void check_and_throw_if_self_directed_tx_with_payment_id_requested(const construct_tx_param& ctp); + void check_and_throw_if_smth_not_good_with_comment_or_payment_id(const construct_tx_param& ctp); void push_new_block_id(const crypto::hash& id, uint64_t height); bool lookup_item_around(uint64_t i, std::pair& result); //void get_short_chain_history(std::list& ids); diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 7479159b..12581bc5 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -71,10 +71,10 @@ namespace tools std::string to_string() const { std::ostringstream ss; - ss << m_loc << '[' << boost::replace_all_copy(std::string(typeid(*this).name()), "struct ", ""); + ss << '[' << boost::replace_all_copy(std::string(typeid(*this).name()), "struct ", ""); if (!m_error_code.empty()) ss << "[" << m_error_code << "]"; - ss << "] " << Base::what(); + ss << "] " << m_loc << ENDL << " " << Base::what(); return ss.str(); } diff --git a/src/wallet/wallet_public_structs_defs.h b/src/wallet/wallet_public_structs_defs.h index 3ac0f839..f3908c24 100644 --- a/src/wallet/wallet_public_structs_defs.h +++ b/src/wallet/wallet_public_structs_defs.h @@ -2159,5 +2159,35 @@ namespace wallet_public }; }; + struct COMMAND_CLEAR_UTXO_COLD_SIG_RESERVATION + { + DOC_COMMAND("Clears cold sig reservation flag for all unspent transaction outputs, that have one. Please, use with CAUTION!"); + + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response_item + { + crypto::public_key pk; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_POD_AS_HEX_STRING(pk) DOC_DSCR("Output's one-time public key") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string status; + std::vector affected_outputs; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) DOC_DSCR("Status of the call") DOC_EXMP("OK") DOC_END + KV_SERIALIZE(affected_outputs) DOC_DSCR("List of affected outputs (for reference).") DOC_EXMP_AUTO(1) DOC_END + END_KV_SERIALIZE_MAP() + }; + }; + } // namespace wallet_rpc } // namespace tools diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index fe1383db..45cbcdea 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -38,25 +38,25 @@ POP_VS_WARNINGS catch (const tools::error::daemon_busy& e) \ { \ er.code = WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY; \ - er.message = std::string("WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY") + e.what(); \ + er.message = std::string("WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY: ") + e.what(); \ return false; \ } \ catch (const tools::error::not_enough_money& e) \ { \ er.code = WALLET_RPC_ERROR_CODE_NOT_ENOUGH_MONEY; \ - er.message = std::string("WALLET_RPC_ERROR_CODE_NOT_ENOUGH_MONEY") + e.error_code(); \ + er.message = std::string("WALLET_RPC_ERROR_CODE_NOT_ENOUGH_MONEY: ") + e.what(); \ return false; \ } \ catch (const tools::error::wallet_error& e) \ { \ er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; \ - er.message = e.error_code(); \ + er.message = std::string("WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR: ") + e.what(); \ return false; \ } \ catch (const std::exception& e) \ { \ - er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; \ - er.message = std::string("WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR: ") + e.what(); \ + er.code = WALLET_RPC_ERROR_CODE_GENERIC_ERROR; \ + er.message = std::string("WALLET_RPC_ERROR_CODE_GENERIC_ERROR: ") + e.what(); \ return false; \ } \ catch (...) \ @@ -73,11 +73,11 @@ void exception_handler() namespace tools { //----------------------------------------------------------------------------------- - const command_line::arg_descriptor wallet_rpc_server::arg_rpc_bind_port ("rpc-bind-port", "Starts wallet as rpc server for wallet operations, sets bind port for server"); - const command_line::arg_descriptor wallet_rpc_server::arg_rpc_bind_ip ("rpc-bind-ip", "Specify ip to bind rpc server", "127.0.0.1"); - const command_line::arg_descriptor wallet_rpc_server::arg_miner_text_info ( "miner-text-info", "Wallet password"); - const command_line::arg_descriptor wallet_rpc_server::arg_deaf_mode ( "deaf", "Put wallet into 'deaf' mode make it ignore any rpc commands(usable for safe PoS mining)"); - const command_line::arg_descriptor wallet_rpc_server::arg_jwt_secret("jwt-secret", "Enables JWT auth over secret string provided"); + const command_line::arg_descriptor wallet_rpc_server::arg_rpc_bind_port ("rpc-bind-port", "Starts wallet as rpc server for wallet operations, sets bind port for server"); + const command_line::arg_descriptor wallet_rpc_server::arg_rpc_bind_ip ("rpc-bind-ip", "Specify ip to bind rpc server", "127.0.0.1"); + const command_line::arg_descriptor wallet_rpc_server::arg_miner_text_info ("miner-text-info", "Wallet password"); + const command_line::arg_descriptor wallet_rpc_server::arg_deaf_mode ("deaf", "Put wallet into 'deaf' mode make it ignore any rpc commands(usable for safe PoS mining)"); + const command_line::arg_descriptor wallet_rpc_server::arg_jwt_secret ("jwt-secret", "Enables JWT auth over secret string provided"); void wallet_rpc_server::init_options(boost::program_options::options_description& desc) { @@ -428,7 +428,7 @@ namespace tools return true; WALLET_RPC_CATCH_TRY_ENTRY(); } - + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_transfer(const wallet_public::COMMAND_RPC_TRANSFER::request& req, wallet_public::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx) { WALLET_RPC_BEGIN_TRY_ENTRY(); @@ -505,6 +505,12 @@ namespace tools er.message = std::string("embedded payment id: ") + embedded_payment_id + " conflicts with previously set payment id: " + payment_id; return false; } + if (it != req.destinations.begin()) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; + er.message = std::string("payment id: ") + embedded_payment_id + " currently can only be set for the first destination (an so you can use integrated address only for the fist destination)"; + return false; + } payment_id = embedded_payment_id; } de.amount = it->amount; @@ -523,9 +529,10 @@ namespace tools if (!req.comment.empty() && payment_id.empty()) { - currency::tx_comment comment = AUTO_VAL_INIT(comment); - comment.comment = req.comment; - extra.push_back(comment); + // tx_comment is temporary disabled -- sowle + //currency::tx_comment comment = AUTO_VAL_INIT(comment); + //comment.comment = req.comment; + //extra.push_back(comment); } if (req.push_payer && !wrap) @@ -1576,6 +1583,31 @@ namespace tools WALLET_RPC_CATCH_TRY_ENTRY(); } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_clear_utxo_cold_sig_reservation(const wallet_public::COMMAND_CLEAR_UTXO_COLD_SIG_RESERVATION::request& req, wallet_public::COMMAND_CLEAR_UTXO_COLD_SIG_RESERVATION::response& res, epee::json_rpc::error& er, connection_context& cntx) + { + WALLET_RPC_BEGIN_TRY_ENTRY(); + + std::vector affected_transfer_ids; + w.get_wallet()->clear_utxo_cold_sig_reservation(affected_transfer_ids); + + for (auto tid : affected_transfer_ids) + { + transfer_details td{}; + if (!w.get_wallet()->get_transfer_info_by_index(tid, td)) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "internal error: get_transfer_info_by_index failed for tid " + epee::string_tools::num_to_string_fast(tid); + return false; + } + + res.affected_outputs.emplace_back(); + currency::get_out_pub_key_from_tx_out_v(td.output(), res.affected_outputs.back().pk); + } + + return true; + WALLET_RPC_CATCH_TRY_ENTRY(); + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_decrypt_data(const wallet_public::COMMAND_DECRYPT_DATA::request& req, wallet_public::COMMAND_DECRYPT_DATA::response& res, epee::json_rpc::error& er, connection_context& cntx) { WALLET_RPC_BEGIN_TRY_ENTRY(); diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 2a3cbc63..2bc4edb8 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -174,6 +174,7 @@ namespace tools //utility call MAP_JON_RPC_WE("proxy_to_daemon", on_proxy_to_daemon, wallet_public::COMMAND_PROXY_TO_DAEMON) + MAP_JON_RPC_WE("clear_utxo_cold_sig_reservation", on_clear_utxo_cold_sig_reservation, wallet_public::COMMAND_CLEAR_UTXO_COLD_SIG_RESERVATION) END_JSON_RPC_MAP() END_URI_MAP2() @@ -242,6 +243,7 @@ namespace tools bool on_decrypt_data(const wallet_public::COMMAND_DECRYPT_DATA::request& req, wallet_public::COMMAND_DECRYPT_DATA::response& res, epee::json_rpc::error& er, connection_context& cntx); bool on_proxy_to_daemon(const wallet_public::COMMAND_PROXY_TO_DAEMON::request& req, wallet_public::COMMAND_PROXY_TO_DAEMON::response& res, epee::json_rpc::error& er, connection_context& cntx); + bool on_clear_utxo_cold_sig_reservation(const wallet_public::COMMAND_CLEAR_UTXO_COLD_SIG_RESERVATION::request& req, wallet_public::COMMAND_CLEAR_UTXO_COLD_SIG_RESERVATION::response& res, epee::json_rpc::error& er, connection_context& cntx); //std::shared_ptr get_wallet(); diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index 676b64ea..8ef2af3f 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -15,3 +15,4 @@ #define WALLET_RPC_ERROR_CODE_WRONG_ARGUMENT -6 #define WALLET_RPC_ERROR_CODE_NOT_ENOUGH_MONEY -7 #define WALLET_RPC_ERROR_CODE_WRONG_MIXINS_FOR_AUDITABLE_WALLET -8 +#define WALLET_RPC_ERROR_CODE_GENERIC_ERROR -9 diff --git a/src/wallet/wallets_manager.cpp b/src/wallet/wallets_manager.cpp index ad2b03f3..72ccdf7d 100644 --- a/src/wallet/wallets_manager.cpp +++ b/src/wallet/wallets_manager.cpp @@ -1922,10 +1922,11 @@ std::string wallets_manager::get_wallet_restore_info(uint64_t wallet_id, std::st { GET_WALLET_OPT_BY_ID(wallet_id, wo); - if (wo.wallet_state != view::wallet_status_info::wallet_state_ready || wo.long_refresh_in_progress) - return API_RETURN_CODE_CORE_BUSY; + seed_phrase = wo.w.unlocked_get()->get_account().get_seed_phrase(seed_password); - seed_phrase = wo.w->get()->get_account().get_seed_phrase(seed_password); + //if (wo.wallet_state != view::wallet_status_info::wallet_state_ready || wo.long_refresh_in_progress) + // return API_RETURN_CODE_CORE_BUSY; + //seed_phrase = wo.w->get()->get_account().get_seed_phrase(seed_password); return API_RETURN_CODE_OK; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 323dbbd8..fb38a979 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -32,7 +32,7 @@ target_link_libraries(coretests rpc wallet currency_core common crypto zlibstati target_link_libraries(functional_tests rpc wallet currency_core crypto common zlibstatic ethash libminiupnpc-static ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES} OpenSSL::SSL OpenSSL::Crypto) target_link_libraries(hash-tests crypto ethash) target_link_libraries(hash-target-tests crypto currency_core ethash ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) -target_link_libraries(performance_tests rpc wallet currency_core common crypto zlibstatic ethash ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES} OpenSSL::SSL OpenSSL::Crypto) +target_link_libraries(performance_tests wallet rpc currency_core common crypto zlibstatic ethash ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES} OpenSSL::SSL OpenSSL::Crypto) target_link_libraries(unit_tests wallet currency_core common crypto gtest_main zlibstatic ethash ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES} OpenSSL::SSL OpenSSL::Crypto) target_link_libraries(net_load_tests_clt currency_core common crypto gtest_main ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) target_link_libraries(net_load_tests_srv currency_core common crypto gtest_main ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) diff --git a/tests/core_tests/block_validation.cpp b/tests/core_tests/block_validation.cpp index ed290387..2d35c703 100644 --- a/tests/core_tests/block_validation.cpp +++ b/tests/core_tests/block_validation.cpp @@ -1202,3 +1202,151 @@ bool block_reward_in_alt_chain_basic::assert_reward(currency::core& core, size_t return true; } + +//----------------------------------------------------------------------------------------------------- +block_choice_rule_bigger_fee::block_choice_rule_bigger_fee() +{ + REGISTER_CALLBACK("c1", block_choice_rule_bigger_fee::c1); +} + +struct block_choice_rule_bigger_fee::argument_assert +{ + std::list transactions{}; + + argument_assert() = default; + + argument_assert(const std::list& txs) + : transactions(txs) + {} + + BEGIN_SERIALIZE() + FIELD(transactions) + END_SERIALIZE() +}; + +// Test idea: fork-choice rule based on transactions’ median fees +/* Sets up three competing chains: + * - Main(blk_1a): 4 transactions with fee 6 (fees = [6, 6, 6, 6], median = (6 + 6) / 2 = 6, score = 6 * 4 = 24) + * - Alt1(blk_1b): 2 transactions with fee 11 (fees = [11, 11], median = (11 + 11) / 2 = 11, score = 11 * 2 = 22) + * - Alt2(blk_1): 2 transactions with fee 10 (fees = [10, 10], median = (10 + 10) / 2 = 10, score = 10 * 2 = 20) + * + * Fork-choice rule: + * - Even count: median = average of the two middle fees, then multiply by the number of transactions. + * - Odd count: median = fee of the central transaction, then multiply by the number of transactions. + * + * The chain with the highest resulting value wins. In this test, Main(blk_1a) wins (24 > 22 and 24 > 20) + * and remains the preferred chain even after Alt2Alt2(blk_1) appears. + */ +bool block_choice_rule_bigger_fee::generate(std::vector& events) const +{ + GENERATE_ACCOUNT(miner); + MAKE_GENESIS_BLOCK(events, blk_0, miner, test_core_time::get_time()); + DO_CALLBACK(events, "configure_core"); + + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + // Main chain + MAKE_TX_FEE(events, tx_1, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE * 10, blk_0r); + MAKE_TX_FEE(events, tx_2, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE * 10, blk_0r); + + std::list txs_1{tx_1, tx_2}; + MAKE_NEXT_BLOCK_TX_LIST(events, blk_1, blk_0r, miner, txs_1); + + DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(blk_1)); + DO_CALLBACK(events, "check_tx_pool_empty"); + + /* 0 10 11 + (blk_0) - ... - (blk_0r) - (blk_1) + {tx0} {tx1, tx2} + */ + + // Alt chain + MAKE_TX_FEE(events, tx_3, miner, miner, MK_TEST_COINS(8), TESTS_DEFAULT_FEE * 6, blk_0r); + MAKE_TX_FEE(events, tx_4, miner, miner, MK_TEST_COINS(8), TESTS_DEFAULT_FEE * 6, blk_0r); + MAKE_TX_FEE(events, tx_5, miner, miner, MK_TEST_COINS(8), TESTS_DEFAULT_FEE * 6, blk_0r); + MAKE_TX_FEE(events, tx_6, miner, miner, MK_TEST_COINS(8), TESTS_DEFAULT_FEE * 6, blk_0r); + + std::list txs_1a{tx_3, tx_4, tx_5, tx_6}; + MAKE_NEXT_BLOCK_TX_LIST(events, blk_1a, blk_0r, miner, txs_1a); + + // tx_1,tx_2 should be in pool + DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(blk_1a)); + DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast(2)); + + // Fees are pre-sorted: + // - If count is even: sum the two middle fees -> e.g., 6 + 6 / 2 * 4 = 24 (blk_1a) + // - If the number is odd: take the central transaction, for example tx1 tx2 tx3 - the fee of tx2 will be median + /* 0 10 11 + (blk_0) - ... - (blk_0r) - (blk_1a) - win because 1 + {tx0} {tx_3, tx_4, tx_5, tx_6} + | + | 11 + \ - (blk_1) + */ + + std::list transactions; + for (const auto& tx : txs_1) + { + transactions.push_back(get_transaction_hash(tx)); + } + argument_assert argument_1a{transactions}; + + DO_CALLBACK_PARAMS_STR(events, "c1", t_serializable_object_to_blob(argument_1a)); + + MAKE_TX_FEE(events, tx_7, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE * 11, blk_0r); + MAKE_TX_FEE(events, tx_8, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE * 11, blk_0r); + + std::list txs_1b{tx_7, tx_8}; + MAKE_NEXT_BLOCK_TX_LIST(events, blk_1b, blk_0r, miner, txs_1b); + + /* 0 10 11 + (blk_0) - ... - (blk_0r) - (blk_1a) - won because (6 + 6) / 2 * 4 = 24 > 22(blk_1b) + {tx0} {tx_3, tx_4, tx_5, tx_6} + | + | 11 + \ - (blk_1b) - lost because after sorting the central element has the value (11 + 11) / 2 * 2 = 22 < 24 + | + | 11 + \ - (blk_1) - lost (10 + 10) / 2 * 2 = 20 < 24 + */ + + // tx_1, tx_2, tx_7, tx_8 should be in pool + DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(blk_1a)); + DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast(4)); + + for (const auto& tx : txs_1b) + { + transactions.push_back(get_transaction_hash(tx)); + } + argument_assert argument_1b{transactions}; + DO_CALLBACK_PARAMS_STR(events, "c1", t_serializable_object_to_blob(argument_1b)); + + return true; +} + +bool block_choice_rule_bigger_fee::c1(currency::core& c, size_t ev_index, const std::vector& events) +{ + argument_assert argument{}; + { + const auto serialized_argument{boost::get(events.at(ev_index)).callback_params}; + + CHECK_AND_ASSERT_EQ(t_unserializable_object_from_blob(argument, serialized_argument), true); + } + + std::list txs; + c.get_pool_transactions(txs); + + CHECK_AND_ASSERT_MES(txs.size() == argument.transactions.size(), false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count()); + + std::list hash_txs; + for (const auto& tx : txs) + { + hash_txs.push_back(get_transaction_hash(tx)); + } + + hash_txs.sort(); + argument.transactions.sort(); + CHECK_AND_ASSERT_MES(hash_txs == argument.transactions, false, "Unexpected transactions in the mempool"); + + return true; +} diff --git a/tests/core_tests/block_validation.h b/tests/core_tests/block_validation.h index 59e4c149..1eac64bd 100644 --- a/tests/core_tests/block_validation.h +++ b/tests/core_tests/block_validation.h @@ -217,3 +217,13 @@ private: bool assert_reward(currency::core& core, size_t event_index, const std::vector& events) const; struct argument_assert; }; + +struct block_choice_rule_bigger_fee : public wallet_test +{ + block_choice_rule_bigger_fee(); + bool generate(std::vector& events) const; + bool c1(currency::core& c, size_t ev_index, const std::vector& events); + +private: + struct argument_assert; +}; \ No newline at end of file diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index a70104fc..87bfee59 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -684,7 +684,7 @@ bool test_generator::find_kernel(const std::list& accs, for (size_t wallet_index = 0, size = wallets.size(); wallet_index < size; ++wallet_index) { std::shared_ptr w = wallets[wallet_index].wallet; - LOG_PRINT_L0("wallet #" << wallet_index << " @ block " << w->get_top_block_height() << ENDL << wallets[wallet_index].wallet->dump_trunsfers()); + LOG_PRINT_L0("wallet #" << wallet_index << " @ block " << w->get_top_block_height() << ENDL << wallets[wallet_index].wallet->dump_transfers()); } return false; @@ -940,7 +940,7 @@ bool test_generator::construct_block(int64_t manual_timestamp_adjustment, const crypto::hash& prev_id/* = crypto::hash()*/, const wide_difficulty_type& diffic/* = 1*/, const transaction& miner_tx/* = transaction()*/, const std::vector& tx_hashes/* = std::vector()*/, - size_t txs_sizes/* = 0*/) + size_t txs_sizes/* = 0*/, currency::blobdata extra_nonce/* = blobdata()*/) { size_t height = get_block_height(prev_block) + 1; blk.major_version = actual_params & bf_major_ver ? major_ver : m_hardforks.get_block_major_version_by_height(height); @@ -966,7 +966,7 @@ bool test_generator::construct_block(int64_t manual_timestamp_adjustment, size_t current_block_size = txs_sizes + get_object_blobsize(blk.miner_tx); // TODO: This will work, until size of constructed block is less then CURRENCY_BLOCK_GRANTED_FULL_REWARD_ZONE if (!construct_miner_tx(height, misc_utils::median(block_sizes), already_generated_coins, current_block_size, 0, - miner_acc.get_public_address(), miner_acc.get_public_address(), blk.miner_tx, base_block_reward, block_reward, tx_version, tx_hardfork_id, blobdata(), 1)) + miner_acc.get_public_address(), miner_acc.get_public_address(), blk.miner_tx, base_block_reward, block_reward, tx_version, tx_hardfork_id, extra_nonce, 1)) return false; } @@ -1421,7 +1421,7 @@ bool fill_tx_sources(std::vector& sources, const std::vector& sources, const std::vector& events, const currency::block& blk_head, const currency::account_keys& from, uint64_t amount, size_t nmix, const std::vector& sources_to_avoid, - bool check_for_spends, bool check_for_unlocktime, bool use_ref_by_id, uint64_t* p_sources_amount_found /* = nullptr */) + bool check_for_spends, bool check_for_unlocktime, bool use_ref_by_id, uint64_t* p_sources_amount_found /* = nullptr */, const mixins_per_input* nmix_map /* = nullptr */) { uint64_t fts_flags = (check_for_spends ? fts_check_for_spends : fts_none) | @@ -1429,17 +1429,18 @@ bool fill_tx_sources(std::vector& sources, const std: (use_ref_by_id ? fts_use_ref_by_id : fts_none) | fts_check_for_hf4_min_coinage; - return fill_tx_sources(sources, events, blk_head, from, amount, nmix, sources_to_avoid, fts_flags, p_sources_amount_found); + return fill_tx_sources(sources, events, blk_head, from, amount, nmix, sources_to_avoid, fts_flags, p_sources_amount_found, nmix_map); } bool fill_tx_sources(std::vector& sources, const std::vector& events, const currency::block& blk_head, const currency::account_keys& from, uint64_t amount, size_t nmix, - const std::vector& sources_to_avoid, uint64_t fts_flags, uint64_t* p_sources_amount_found /* = nullptr */) + const std::vector& sources_to_avoid, uint64_t fts_flags, uint64_t* p_sources_amount_found /* = nullptr */, + const mixins_per_input* nmix_map /* = nullptr */) { std::unordered_map amounts; amounts[native_coin_asset_id] = amount; std::unordered_map sources_amounts; - if (!fill_tx_sources(sources, events, blk_head, from, amounts, nmix, sources_to_avoid, fts_flags, &sources_amounts)) + if (!fill_tx_sources(sources, events, blk_head, from, amounts, nmix, sources_to_avoid, fts_flags, &sources_amounts, nmix_map)) return false; if (p_sources_amount_found) *p_sources_amount_found = sources_amounts[native_coin_asset_id]; @@ -1448,7 +1449,8 @@ bool fill_tx_sources(std::vector& sources, const std: bool fill_tx_sources(std::vector& sources, const std::vector& events, const currency::block& blk_head, const currency::account_keys& from, const std::unordered_map& amounts, size_t nmix, - const std::vector& sources_to_avoid, uint64_t fts_flags, std::unordered_map* p_sources_amounts /* = nullptr */) + const std::vector& sources_to_avoid, uint64_t fts_flags, std::unordered_map* p_sources_amounts /* = nullptr */, + const mixins_per_input* nmix_map /* = nullptr */) { map_output_idx_t outs; map_output_t outs_mine; @@ -1558,7 +1560,23 @@ bool fill_tx_sources(std::vector& sources, const std: ts.real_out_amount_blinding_mask = oi.amount_blinding_mask; ts.real_output_in_tx_index = oi.out_no; ts.real_out_tx_key = get_tx_pub_key_from_extra(*oi.p_tx); // source tx public key - if (!fill_output_entries(outs[o.first], sender_out, nmix, fts_flags & fts_check_for_unlocktime, fts_flags & fts_use_ref_by_id, + + // If we use nmix_map, we should use local_nmix instead of nmix + // to allow different nmix for different inputs in the same transaction. + // If nmix_map is not provided, we use nmix as local_nmix. + size_t local_nmix = nmix; + if (nmix_map) + { + // Try to find an override for this input index 'i' + auto it = nmix_map->find(i); + if (it != nmix_map->end()) + { + local_nmix = it->second; + } + // leave local_nmix at its default + } + + if (!fill_output_entries(outs[o.first], sender_out, local_nmix, fts_flags & fts_check_for_unlocktime, fts_flags & fts_use_ref_by_id, next_block_height, head_block_ts, ts.real_output, ts.outputs)) { continue; @@ -1592,7 +1610,9 @@ bool fill_tx_sources_and_destinations(const std::vector& event bool check_for_spends, bool check_for_unlocktime, size_t minimum_sigs, - bool use_ref_by_id) + bool use_ref_by_id, + const mixins_per_input* nmix_map +) { CHECK_AND_ASSERT_MES(!to.empty(), false, "destination addresses vector is empty"); CHECK_AND_ASSERT_MES(amount + fee > amount, false, "amount + fee overflow!"); @@ -1601,7 +1621,7 @@ bool fill_tx_sources_and_destinations(const std::vector& event bool b_multisig = to.size() > 1; uint64_t source_amount_found = 0; - bool r = fill_tx_sources(sources, events, blk_head, from, amount + fee, nmix, std::vector(), check_for_spends, check_for_unlocktime, use_ref_by_id, &source_amount_found); + bool r = fill_tx_sources(sources, events, blk_head, from, amount + fee, nmix, std::vector(), check_for_spends, check_for_unlocktime, use_ref_by_id, &source_amount_found, nmix_map); CHECK_AND_ASSERT_MES(r, false, "couldn't fill transaction sources (nmix = " << nmix << "): " << ENDL << " required: " << print_money(amount + fee) << " = " << std::fixed << std::setprecision(1) << ceil(1.0 * (amount + fee) / TESTS_DEFAULT_FEE) << " x TESTS_DEFAULT_FEE" << ENDL << " found coins: " << print_money(source_amount_found) << " = " << std::fixed << std::setprecision(1) << ceil(1.0 * source_amount_found / TESTS_DEFAULT_FEE) << " x TESTS_DEFAULT_FEE" << ENDL << @@ -1673,9 +1693,10 @@ bool fill_tx_sources_and_destinations(const std::vector& event std::vector& destinations, bool check_for_spends, bool check_for_unlocktime, - bool use_ref_by_id) + bool use_ref_by_id, + const mixins_per_input* nmix_map) { - return fill_tx_sources_and_destinations(events, blk_head, from, std::list({ to }), amount, fee, nmix, sources, destinations, check_for_spends, check_for_unlocktime, 0, use_ref_by_id); + return fill_tx_sources_and_destinations(events, blk_head, from, std::list({ to }), amount, fee, nmix, sources, destinations, check_for_spends, check_for_unlocktime, 0, use_ref_by_id, nmix_map); } bool fill_tx_sources_and_destinations(const std::vector& events, const currency::block& blk_head, @@ -1685,9 +1706,10 @@ bool fill_tx_sources_and_destinations(const std::vector& event std::vector& destinations, bool check_for_spends, bool check_for_unlocktime, - bool use_ref_by_id) + bool use_ref_by_id, + const mixins_per_input* nmix_map) { - return fill_tx_sources_and_destinations(events, blk_head, from.get_keys(), to.get_public_address(), amount, fee, nmix, sources, destinations, check_for_spends, check_for_unlocktime, use_ref_by_id); + return fill_tx_sources_and_destinations(events, blk_head, from.get_keys(), to.get_public_address(), amount, fee, nmix, sources, destinations, check_for_spends, check_for_unlocktime, use_ref_by_id, nmix_map); } /* @@ -2190,7 +2212,7 @@ bool check_balance_via_wallet(const tools::wallet2& w, const char* account_name, if (!r) { - LOG_PRINT(account_name << "'s transfers for asset_id " << asset_id << ": " << ENDL << w.dump_trunsfers(false, asset_id), LOG_LEVEL_0); + LOG_PRINT(account_name << "'s transfers for asset_id " << asset_id << ": " << ENDL << w.dump_transfers(false, asset_id), LOG_LEVEL_0); } return r; @@ -2451,7 +2473,7 @@ bool refresh_wallet_and_check_balance(const char* intro_log_message, const char* if (print_transfers) { - LOG_PRINT_CYAN(wallet_name << "'s transfers: " << ENDL << wallet->dump_trunsfers(), LOG_LEVEL_0); + LOG_PRINT_CYAN(wallet_name << "'s transfers: " << ENDL << wallet->dump_transfers(), LOG_LEVEL_0); } CHECK_AND_ASSERT_MES(check_balance_via_wallet(*wallet.get(), wallet_name, expected_total, expected_mined, expected_unlocked, expected_awaiting_in, expected_awaiting_out), false, ""); @@ -2498,6 +2520,102 @@ uint64_t decode_native_output_amount_or_throw(const account_base& acc, const tra return amount; } +bool generate_pos_block_with_extra_nonce(test_generator& generator, const std::vector& events, const currency::account_base& miner, const currency::account_base& recipient, const currency::block& prev_block, const currency::transaction& stake_tx, const currency::blobdata& pos_nonce, currency::block& result) +{ + // get params for PoS + crypto::hash prev_id = get_block_hash(prev_block); + wide_difficulty_type pos_diff{}; + crypto::hash last_pow_block_hash{}, last_pos_block_kernel_hash{}; + bool r = generator.get_params_for_next_pos_block( + prev_id, pos_diff, last_pow_block_hash, last_pos_block_kernel_hash + ); + CHECK_AND_ASSERT_MES(r, false, "get_params_for_next_pos_block failed"); + + // tx key and key image for stake out + crypto::public_key stake_pk = get_tx_pub_key_from_extra(stake_tx); + keypair kp; + crypto::key_image ki; + size_t stake_output_idx = 0; + generate_key_image_helper(miner.get_keys(), stake_pk, stake_output_idx, kp, ki); + + // glob index for stake out + uint64_t stake_output_gidx = UINT64_MAX; + r = find_global_index_for_output(events, prev_id, stake_tx, stake_output_idx, stake_output_gidx); + CHECK_AND_ASSERT_MES(r, false, "find_global_index_for_output failed"); + + pos_block_builder pb; + uint64_t height = get_block_height(prev_block) + 1; + pb.step1_init_header(generator.get_hardforks(), height, prev_id); + pb.step2_set_txs({}); + + if (generator.get_hardforks().is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, height)) + { + std::vector sources; + fill_tx_sources( + sources, events, prev_block, miner.get_keys(), + UINT64_MAX, 0, false, false, true + ); + + auto it = std::find_if(sources.begin(), sources.end(), + [&](const tx_source_entry &e){ + return e.real_out_tx_key == stake_pk + && e.real_output_in_tx_index == stake_output_idx; + }); + CHECK_AND_ASSERT_MES(it != sources.end(), false, "source entry not found"); + const tx_source_entry& se = *it; + + pb.step3a(pos_diff, last_pow_block_hash, last_pos_block_kernel_hash); + pb.step3b( + se.amount, ki, + se.real_out_tx_key, se.real_output_in_tx_index, + se.real_out_amount_blinding_mask, + miner.get_keys().view_secret_key, + stake_output_gidx, + prev_block.timestamp, + POS_SCAN_WINDOW, POS_SCAN_STEP + ); + + // insert extra_nonce + pb.step4_generate_coinbase_tx( + generator.get_timestamps_median(prev_id), + generator.get_already_generated_coins(prev_block), + recipient.get_public_address(), + pos_nonce, + CURRENCY_MINER_TX_MAX_OUTS + ); + + pb.step5_sign(se, miner.get_keys()); + } + else // HF3: SLSAG + { + uint64_t amount = boost::get(stake_tx.vout[stake_output_idx]).amount; + pb.step3_build_stake_kernel( + amount, + stake_output_gidx, + ki, + pos_diff, + last_pow_block_hash, + last_pos_block_kernel_hash, + prev_block.timestamp + ); + + // insert extra_nonce + pb.step4_generate_coinbase_tx( + generator.get_timestamps_median(prev_id), + generator.get_already_generated_coins(prev_block), + recipient.get_public_address(), + pos_nonce, + CURRENCY_MINER_TX_MAX_OUTS + ); + + crypto::public_key out_pk = boost::get(boost::get(stake_tx.vout[stake_output_idx]).target).key; + pb.step5_sign(stake_pk, stake_output_idx, out_pk, miner); + } + + result = pb.m_block; + return true; +} + bool generate_pos_block_with_given_coinstake(test_generator& generator, const std::vector &events, const currency::account_base& miner, const currency::block& prev_block, const currency::transaction& stake_tx, size_t stake_output_idx, currency::block& result, uint64_t stake_output_gidx /* = UINT64_MAX */) { diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index e4286057..6bbda6e7 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -227,6 +227,7 @@ VARIANT_TAG(binary_archive, core_hardforks_config, 0xd2); typedef boost::variant test_event_entry; typedef std::unordered_map map_hash2tx_t; +typedef std::unordered_map mixins_per_input; enum test_tx_split_strategy { tests_default_split_strategy /*height-based, TODO*/, tests_void_split_strategy, tests_null_split_strategy, tests_digits_split_strategy, tests_random_split_strategy }; struct test_gentime_settings @@ -570,7 +571,8 @@ public: const currency::account_base& miner_acc, int actual_params = bf_none, uint8_t major_ver = 0, uint8_t minor_ver = 0, uint64_t timestamp = 0, const crypto::hash& prev_id = crypto::hash(), const currency::wide_difficulty_type& diffic = 1, const currency::transaction& miner_tx = currency::transaction(), - const std::vector& tx_hashes = std::vector(), size_t txs_sizes = 0); + const std::vector& tx_hashes = std::vector(), size_t txs_sizes = 0, + currency::blobdata extra_nonce = currency::blobdata()); bool construct_block_manually_tx(currency::block& blk, const currency::block& prev_block, const currency::account_base& miner_acc, const std::vector& tx_hashes, size_t txs_size); bool find_nounce(currency::block& blk, std::vector& blocks, currency::wide_difficulty_type dif, uint64_t height) const; @@ -754,13 +756,16 @@ bool fill_tx_sources(std::vector& sources, const std: bool fill_tx_sources(std::vector& sources, const std::vector& events, const currency::block& blk_head, const currency::account_keys& from, uint64_t amount, size_t nmix, const std::vector& sources_to_avoid, bool check_for_spends = true, bool check_for_unlocktime = true, - bool use_ref_by_id = false, uint64_t* p_sources_amount_found = nullptr); + bool use_ref_by_id = false, uint64_t* p_sources_amount_found = nullptr, + const mixins_per_input* nmix_map = nullptr); bool fill_tx_sources(std::vector& sources, const std::vector& events, const currency::block& blk_head, const currency::account_keys& from, uint64_t amount, size_t nmix, - const std::vector& sources_to_avoid, uint64_t fts_flags, uint64_t* p_sources_amount_found = nullptr); + const std::vector& sources_to_avoid, uint64_t fts_flags, uint64_t* p_sources_amount_found = nullptr, + const mixins_per_input* nmix_map = nullptr); bool fill_tx_sources(std::vector& sources, const std::vector& events, const currency::block& blk_head, const currency::account_keys& from, const std::unordered_map& amounts, size_t nmix, - const std::vector& sources_to_avoid, uint64_t fts_flags, std::unordered_map* p_sources_amounts = nullptr); + const std::vector& sources_to_avoid, uint64_t fts_flags, std::unordered_map* p_sources_amounts = nullptr, + const mixins_per_input* nmix_map = nullptr); bool fill_tx_sources_and_destinations(const std::vector& events, const currency::block& blk_head, const currency::account_keys& from, const std::list& to, @@ -769,7 +774,8 @@ bool fill_tx_sources_and_destinations(const std::vector& event bool check_for_spends = true, bool check_for_unlocktime = true, size_t minimum_sigs = SIZE_MAX, - bool use_ref_by_id = false); + bool use_ref_by_id = false, + const mixins_per_input* nmix_map = nullptr); bool fill_tx_sources_and_destinations(const std::vector& events, const currency::block& blk_head, const currency::account_keys& from, const currency::account_public_address& to, uint64_t amount, uint64_t fee, size_t nmix, @@ -777,7 +783,8 @@ bool fill_tx_sources_and_destinations(const std::vector& event std::vector& destinations, bool check_for_spends = true, bool check_for_unlocktime = true, - bool use_ref_by_id = false); + bool use_ref_by_id = false, + const mixins_per_input* nmix_map = nullptr); bool fill_tx_sources_and_destinations(const std::vector& events, const currency::block& blk_head, const currency::account_base& from, const currency::account_base& to, uint64_t amount, uint64_t fee, size_t nmix, @@ -785,7 +792,8 @@ bool fill_tx_sources_and_destinations(const std::vector& event std::vector& destinations, bool check_for_spends = true, bool check_for_unlocktime = true, - bool use_ref_by_id = false); + bool use_ref_by_id = false, + const mixins_per_input* nmix_map = nullptr); uint64_t get_balance(const currency::account_keys& addr, const std::vector& blockchain, const map_hash2tx_t& mtx, bool dbg_log = false); uint64_t get_balance(const currency::account_base& addr, const std::vector& blockchain, const map_hash2tx_t& mtx, bool dbg_log = false); void balance_via_wallet(const tools::wallet2& w, const crypto::public_key& asset_id, uint64_t* p_total, uint64_t* p_unlocked = 0, uint64_t* p_awaiting_in = 0, uint64_t* p_awaiting_out = 0, uint64_t* p_mined = 0); @@ -828,6 +836,7 @@ uint64_t get_last_block_of_type(bool looking_for_pos, const test_generator::bloc bool decode_output_amount_and_asset_id(const currency::account_base& acc, const currency::transaction& tx, size_t output_index, uint64_t &amount, crypto::public_key* p_asset_id = nullptr); uint64_t decode_native_output_amount_or_throw(const currency::account_base& acc, const currency::transaction& tx, size_t output_index); +bool generate_pos_block_with_extra_nonce(test_generator& generator, const std::vector& events, const currency::account_base& miner, const currency::account_base& recipient, const currency::block& prev_block, const currency::transaction& stake_tx, const currency::blobdata& pos_nonce, currency::block& result); bool generate_pos_block_with_given_coinstake(test_generator& generator, const std::vector &events, const currency::account_base& miner, const currency::block& prev_block, const currency::transaction& stake_tx, size_t stake_output_idx, currency::block& result, uint64_t stake_output_gidx = UINT64_MAX); bool check_ring_signature_at_gen_time(const std::vector& events, const crypto::hash& last_block_id, const currency::txin_to_key& in_t_k, const crypto::hash& hash_for_sig, const std::vector &sig); @@ -1356,12 +1365,14 @@ void append_vector_by_another_vector(U& dst, const V& src) #define MAKE_TX_LIST_START_WITH_ATTACHS(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, HEAD, ATTACHS) MAKE_TX_LIST_START_MIX_ATTR(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, HEAD, CURRENCY_TO_KEY_OUT_RELAXED, ATTACHS) -#define MAKE_TX_ATTACH_FEE(EVENTS, TX_VAR, FROM, TO, AMOUNT, FEE, HEAD, ATTACH) \ - PRINT_EVENT_N(EVENTS); \ - currency::transaction TX_VAR = AUTO_VAL_INIT(TX_VAR); \ - CHECK_AND_ASSERT_MES(construct_tx_to_key(generator.get_hardforks(), EVENTS, TX_VAR, HEAD, FROM, TO, AMOUNT, FEE, 0, generator.last_tx_generated_secret_key, CURRENCY_TO_KEY_OUT_RELAXED, empty_extra, ATTACH), false, "construct_tx_to_key failed"); \ +#define MAKE_TX_EXTRA_ATTACH_FEE(EVENTS, TX_VAR, FROM, TO, AMOUNT, FEE, HEAD, EXTRA, ATTACH) \ + PRINT_EVENT_N(EVENTS); \ + currency::transaction TX_VAR{}; \ + CHECK_AND_ASSERT_MES(construct_tx_to_key(generator.get_hardforks(), EVENTS, TX_VAR, HEAD, FROM, TO, AMOUNT, FEE, 0, generator.last_tx_generated_secret_key, CURRENCY_TO_KEY_OUT_RELAXED, EXTRA, ATTACH), false, "construct_tx_to_key failed"); \ EVENTS.push_back(TX_VAR) +#define MAKE_TX_ATTACH_FEE(EVENTS, TX_VAR, FROM, TO, AMOUNT, FEE, HEAD, ATTACH) MAKE_TX_EXTRA_ATTACH_FEE(EVENTS, TX_VAR, FROM, TO, AMOUNT, FEE, HEAD, empty_extra, ATTACH) + #define MAKE_TX_ATTACH(EVENTS, TX_VAR, FROM, TO, AMOUNT, HEAD, ATTACH) MAKE_TX_ATTACH_FEE(EVENTS, TX_VAR, FROM, TO, AMOUNT, TESTS_DEFAULT_FEE, HEAD, ATTACH) #define MAKE_MINER_TX_AND_KEY_MANUALLY(TX, PREV_BLOCK, P_KEYPAIR) \ @@ -1417,30 +1428,25 @@ void append_vector_by_another_vector(U& dst, const V& src) #define MAKE_TEST_WALLET_TX(EVENTS_VEC, TX_VAR, WLT_WAR, MONEY, DEST_ACC) \ PRINT_EVENT_N(EVENTS_VEC); \ - transaction TX_VAR = AUTO_VAL_INIT(TX_VAR); \ + transaction TX_VAR{}; \ { \ std::vector destinations(1, tx_destination_entry(MONEY, DEST_ACC.get_public_address())); \ WLT_WAR->transfer(destinations, 0, 0, TESTS_DEFAULT_FEE, std::vector(), std::vector(), TX_VAR); \ } \ EVENTS_VEC.push_back(TX_VAR) -#define MAKE_TEST_WALLET_TX_ATTACH(EVENTS_VEC, TX_VAR, WLT_WAR, MONEY, DEST_ACC, ATTACH) \ +#define MAKE_TEST_WALLET_TX_EXTRA_ATTACH(EVENTS_VEC, TX_VAR, WLT_WAR, MONEY, DEST_ACC, EXTRA, ATTACH) \ PRINT_EVENT_N(EVENTS_VEC); \ - transaction TX_VAR = AUTO_VAL_INIT(TX_VAR); \ + transaction TX_VAR{}; \ { \ std::vector destinations(1, tx_destination_entry(MONEY, DEST_ACC.get_public_address())); \ - WLT_WAR->transfer(destinations, 0, 0, TESTS_DEFAULT_FEE, std::vector(), ATTACH, TX_VAR); \ + WLT_WAR->transfer(destinations, 0, 0, TESTS_DEFAULT_FEE, EXTRA, ATTACH, TX_VAR); \ } \ EVENTS_VEC.push_back(TX_VAR) -#define MAKE_TEST_WALLET_TX_EXTRA(EVENTS_VEC, TX_VAR, WLT_WAR, MONEY, DEST_ACC, EXTRA) \ - PRINT_EVENT_N(EVENTS_VEC); \ - transaction TX_VAR = AUTO_VAL_INIT(TX_VAR); \ - { \ - std::vector destinations(1, tx_destination_entry(MONEY, DEST_ACC.get_public_address())); \ - WLT_WAR->transfer(destinations, 0, 0, TESTS_DEFAULT_FEE, EXTRA, std::vector(), TX_VAR); \ - } \ - EVENTS_VEC.push_back(TX_VAR) +#define MAKE_TEST_WALLET_TX_ATTACH(EVENTS_VEC, TX_VAR, WLT_WAR, MONEY, DEST_ACC, ATTACH) MAKE_TEST_WALLET_TX_EXTRA_ATTACH(EVENTS_VEC, TX_VAR, WLT_WAR, MONEY, DEST_ACC, empty_extra, ATTACH) + +#define MAKE_TEST_WALLET_TX_EXTRA(EVENTS_VEC, TX_VAR, WLT_WAR, MONEY, DEST_ACC, EXTRA) MAKE_TEST_WALLET_TX_EXTRA_ATTACH(EVENTS_VEC, TX_VAR, WLT_WAR, MONEY, DEST_ACC, EXTRA, empty_attachment) #define CHECK_TEST_WALLET_BALANCE_AT_GEN_TIME(WLT_WAR, TOTAL_BALANCE) \ if (!check_balance_via_wallet(*WLT_WAR.get(), #WLT_WAR, TOTAL_BALANCE)) \ diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index f8d46c10..c5524110 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -1077,7 +1077,7 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(gen_wallet_oversized_payment_id); GENERATE_AND_PLAY(gen_wallet_transfers_and_outdated_unconfirmed_txs); GENERATE_AND_PLAY(gen_wallet_transfers_and_chain_switch); - GENERATE_AND_PLAY(gen_wallet_decrypted_attachments); + GENERATE_AND_PLAY(gen_wallet_decrypted_payload_items); GENERATE_AND_PLAY_HF(gen_wallet_alias_and_unconfirmed_txs, "3-*"); GENERATE_AND_PLAY_HF(gen_wallet_alias_via_special_wallet_funcs, "3-*"); GENERATE_AND_PLAY(gen_wallet_fake_outputs_randomness); @@ -1104,6 +1104,7 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY_HF(wallet_rpc_exchange_suite, "3,4"); GENERATE_AND_PLAY_HF(wallet_true_rpc_pos_mining, "4-*"); GENERATE_AND_PLAY_HF(wallet_rpc_cold_signing, "3,5-*"); + // GENERATE_AND_PLAY_HF(wallet_rpc_multiple_receivers, "5-*"); work in progress -- sowle GENERATE_AND_PLAY(wallet_chain_switch_with_spending_the_same_ki); GENERATE_AND_PLAY(wallet_sending_to_integrated_address); GENERATE_AND_PLAY_HF(block_template_blacklist_test, "4-*"); @@ -1118,7 +1119,7 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(gen_pos_coinstake_already_spent); GENERATE_AND_PLAY(gen_pos_incorrect_timestamp); GENERATE_AND_PLAY(gen_pos_too_early_pos_block); - GENERATE_AND_PLAY(gen_pos_extra_nonce); + GENERATE_AND_PLAY_HF(gen_pos_extra_nonce, "3-*"); GENERATE_AND_PLAY(gen_pos_min_allowed_height); GENERATE_AND_PLAY(gen_pos_invalid_coinbase); // GENERATE_AND_PLAY(pos_wallet_minting_same_amount_diff_outs); // Long test! Takes ~10 hours to simulate 6000 blocks on 2015 middle-end computer @@ -1191,7 +1192,8 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY_HF(gen_block_miner_tx_has_out_to_initiator, "0,3"); GENERATE_AND_PLAY_HF(gen_block_has_invalid_tx, "0,3"); GENERATE_AND_PLAY_HF(gen_block_is_too_big, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_wrong_version_agains_hardfork, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_wrong_version_agains_hardfork, "0,3"); + GENERATE_AND_PLAY_HF(block_choice_rule_bigger_fee, "4-*"); //GENERATE_AND_PLAY(gen_block_invalid_binary_format); // Takes up to 3 hours, if CURRENCY_MINED_MONEY_UNLOCK_WINDOW == 500, up to 30 minutes, if CURRENCY_MINED_MONEY_UNLOCK_WINDOW == 10 @@ -1228,6 +1230,8 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY_HF(tx_pool_semantic_validation, "3"); GENERATE_AND_PLAY(input_refers_to_incompatible_by_type_output); GENERATE_AND_PLAY_HF(tx_pool_validation_and_chain_switch, "4-*"); + GENERATE_AND_PLAY_HF(tx_coinbase_separate_sig_flag, "4-*"); + GENERATE_AND_PLAY(tx_input_mixins); // Double spend GENERATE_AND_PLAY(gen_double_spend_in_tx); diff --git a/tests/core_tests/escrow_wallet_altchain_test.cpp b/tests/core_tests/escrow_wallet_altchain_test.cpp index 514ebc82..69fd0ab4 100644 --- a/tests/core_tests/escrow_wallet_altchain_test.cpp +++ b/tests/core_tests/escrow_wallet_altchain_test.cpp @@ -319,7 +319,7 @@ bool escrow_altchain_meta_impl::c1(currency::core& c, size_t ev_index, const std alice_wlt->refresh(blocks_fetched); //fetched blocks disabled since resync might happened on different situation and number of blocks_fetched might be unexpected //CHECK_AND_ASSERT_MES(blocks_fetched == se.expected_blocks, false, "Alice got " << blocks_fetched << " after refresh, but " << se.expected_blocks << " is expected"); - LOG_PRINT_GREEN("Alice's transfers:" << ENDL << alice_wlt->dump_trunsfers(), LOG_LEVEL_1); + LOG_PRINT_GREEN("Alice's transfers:" << ENDL << alice_wlt->dump_transfers(), LOG_LEVEL_1); if (se.a_balance != UINT64_MAX) { uint64_t alice_balance = alice_wlt->balance(); @@ -338,7 +338,7 @@ bool escrow_altchain_meta_impl::c1(currency::core& c, size_t ev_index, const std bob_wlt->refresh(blocks_fetched); //fetched blocks disabled since resync might happened on different situation and number of blocks_fetched might be unexpected //CHECK_AND_ASSERT_MES(blocks_fetched == se.expected_blocks, false, "Bob got " << blocks_fetched << " after refresh, but " << se.expected_blocks << " is expected"); - LOG_PRINT_GREEN("Bob's transfers:" << ENDL << bob_wlt->dump_trunsfers(), LOG_LEVEL_1); + LOG_PRINT_GREEN("Bob's transfers:" << ENDL << bob_wlt->dump_transfers(), LOG_LEVEL_1); if (se.b_balance != UINT64_MAX) { uint64_t bob_balance = bob_wlt->balance(); diff --git a/tests/core_tests/escrow_wallet_tests.cpp b/tests/core_tests/escrow_wallet_tests.cpp index b5c71e29..0f0bbd6c 100644 --- a/tests/core_tests/escrow_wallet_tests.cpp +++ b/tests/core_tests/escrow_wallet_tests.cpp @@ -759,7 +759,7 @@ bool escrow_proposal_expiration::c1(currency::core& c, size_t ev_index, const st bob_wlt->refresh(); uint64_t alice_start_balance = alice_wlt->balance(); - LOG_PRINT_CYAN("Alice's wallet transfers: " << ENDL << alice_wlt->dump_trunsfers(), LOG_LEVEL_0); + LOG_PRINT_CYAN("Alice's wallet transfers: " << ENDL << alice_wlt->dump_transfers(), LOG_LEVEL_0); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool"); @@ -777,7 +777,7 @@ bool escrow_proposal_expiration::c1(currency::core& c, size_t ev_index, const st transaction proposal_tx = AUTO_VAL_INIT(proposal_tx); transaction escrow_template_tx = AUTO_VAL_INIT(escrow_template_tx); alice_wlt->send_escrow_proposal(cpd, 0, 0, expiration_period, TESTS_DEFAULT_FEE, TESTS_DEFAULT_FEE, "", proposal_tx, escrow_template_tx); - LOG_PRINT_CYAN("alice transfers: " << ENDL << alice_wlt->dump_trunsfers(), LOG_LEVEL_0); + LOG_PRINT_CYAN("alice transfers: " << ENDL << alice_wlt->dump_transfers(), LOG_LEVEL_0); uint64_t alice_post_proposal_balance = alice_wlt->balance(); uint64_t alice_post_proposal_balance_expected = alice_start_balance - TESTS_DEFAULT_FEE; CHECK_AND_ASSERT_MES(alice_post_proposal_balance == alice_post_proposal_balance_expected, false, "Incorrect alice_post_proposal_balance: " << print_money(alice_post_proposal_balance) << ", expected: " << print_money(alice_post_proposal_balance_expected)); @@ -840,9 +840,9 @@ bool escrow_proposal_expiration::c2(currency::core& c, size_t ev_index, const st bob_wlt->refresh(); uint64_t alice_start_balance = alice_wlt->balance(); - LOG_PRINT_CYAN("Alice's wallet transfers: " << ENDL << alice_wlt->dump_trunsfers(), LOG_LEVEL_0); + LOG_PRINT_CYAN("Alice's wallet transfers: " << ENDL << alice_wlt->dump_transfers(), LOG_LEVEL_0); uint64_t bob_start_balance = bob_wlt->balance(); - LOG_PRINT_CYAN("Bob's wallet transfers: " << ENDL << bob_wlt->dump_trunsfers(), LOG_LEVEL_0); + LOG_PRINT_CYAN("Bob's wallet transfers: " << ENDL << bob_wlt->dump_transfers(), LOG_LEVEL_0); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); @@ -860,7 +860,7 @@ bool escrow_proposal_expiration::c2(currency::core& c, size_t ev_index, const st transaction proposal_tx = AUTO_VAL_INIT(proposal_tx); transaction escrow_template_tx = AUTO_VAL_INIT(escrow_template_tx); alice_wlt->send_escrow_proposal(cpd, 0, 0, expiration_period, TESTS_DEFAULT_FEE, TESTS_DEFAULT_FEE, "", proposal_tx, escrow_template_tx); - LOG_PRINT_CYAN("%%%%% Escrow proposal sent, Alice's transfers: " << ENDL << alice_wlt->dump_trunsfers(), LOG_LEVEL_0); + LOG_PRINT_CYAN("%%%%% Escrow proposal sent, Alice's transfers: " << ENDL << alice_wlt->dump_transfers(), LOG_LEVEL_0); CHECK_AND_ASSERT_MES(check_wallet_balance_blocked_for_escrow(*alice_wlt.get(), "Alice", cpd.amount_a_pledge + cpd.amount_to_pay), false, ""); crypto::hash ms_id = get_multisig_out_id(escrow_template_tx, get_multisig_out_index(escrow_template_tx.vout)); CHECK_AND_ASSERT_MES(ms_id != null_hash, false, "Can't obtain multisig id from escrow template tx"); @@ -996,7 +996,7 @@ bool escrow_proposal_and_accept_expiration::c1(currency::core& c, size_t ev_inde transaction proposal_tx = AUTO_VAL_INIT(proposal_tx); transaction escrow_template_tx = AUTO_VAL_INIT(escrow_template_tx); alice_wlt->send_escrow_proposal(cpd, 0, 0, expiration_period, TESTS_DEFAULT_FEE, b_release_fee, "", proposal_tx, escrow_template_tx); - LOG_PRINT_CYAN("%%%%% Escrow proposal sent, Alice's transfers: " << ENDL << alice_wlt->dump_trunsfers(), LOG_LEVEL_0); + LOG_PRINT_CYAN("%%%%% Escrow proposal sent, Alice's transfers: " << ENDL << alice_wlt->dump_transfers(), LOG_LEVEL_0); CHECK_AND_ASSERT_MES(check_wallet_balance_blocked_for_escrow(*alice_wlt.get(), "Alice", cpd.amount_a_pledge + cpd.amount_to_pay), false, ""); crypto::hash ms_id = get_multisig_out_id(escrow_template_tx, get_multisig_out_index(escrow_template_tx.vout)); @@ -1021,8 +1021,8 @@ bool escrow_proposal_and_accept_expiration::c1(currency::core& c, size_t ev_inde CHECK_AND_ASSERT_MES(refresh_wallet_and_check_contract_state("Alice", alice_wlt, tools::wallet_public::escrow_contract_details::contract_accepted, ms_id, 0), false, ""); CHECK_AND_ASSERT_MES(refresh_wallet_and_check_contract_state("Bob", bob_wlt, tools::wallet_public::escrow_contract_details::contract_accepted, ms_id, 0), false, ""); - LOG_PRINT_CYAN("%%%%% Escrow proposal accepted (unconfirmed), Alice's transfers: " << ENDL << alice_wlt->dump_trunsfers(), LOG_LEVEL_0); - LOG_PRINT_CYAN("%%%%% Escrow proposal accepted (unconfirmed), Bob's transfers: " << ENDL << bob_wlt->dump_trunsfers(), LOG_LEVEL_0); + LOG_PRINT_CYAN("%%%%% Escrow proposal accepted (unconfirmed), Alice's transfers: " << ENDL << alice_wlt->dump_transfers(), LOG_LEVEL_0); + LOG_PRINT_CYAN("%%%%% Escrow proposal accepted (unconfirmed), Bob's transfers: " << ENDL << bob_wlt->dump_transfers(), LOG_LEVEL_0); // mine a few blocks with no txs @@ -1047,8 +1047,8 @@ bool escrow_proposal_and_accept_expiration::c1(currency::core& c, size_t ev_inde CHECK_AND_ASSERT_MES(refresh_wallet_and_check_contract_state("Alice", alice_wlt, tools::wallet_public::escrow_contract_details::contract_accepted, ms_id, TX_EXPIRATION_TIMESTAMP_CHECK_WINDOW + 1), false, ""); CHECK_AND_ASSERT_MES(refresh_wallet_and_check_contract_state("Bob", bob_wlt, tools::wallet_public::escrow_contract_details::contract_accepted, ms_id, TX_EXPIRATION_TIMESTAMP_CHECK_WINDOW + 1), false, ""); - LOG_PRINT_CYAN("%%%%% Escrow acceptance tx expired and removed from tx pool, Alice's transfers: " << ENDL << alice_wlt->dump_trunsfers(), LOG_LEVEL_0); - LOG_PRINT_CYAN("%%%%% Escrow acceptance tx expired and removed from tx pool, Bob's transfers: " << ENDL << bob_wlt->dump_trunsfers(), LOG_LEVEL_0); + LOG_PRINT_CYAN("%%%%% Escrow acceptance tx expired and removed from tx pool, Alice's transfers: " << ENDL << alice_wlt->dump_transfers(), LOG_LEVEL_0); + LOG_PRINT_CYAN("%%%%% Escrow acceptance tx expired and removed from tx pool, Bob's transfers: " << ENDL << bob_wlt->dump_transfers(), LOG_LEVEL_0); // try to accept expired proposal once again -- an exception should be thrown r = false; @@ -1318,7 +1318,7 @@ bool escrow_incorrect_proposal_acceptance::check_normal_acceptance(currency::cor std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, ALICE_ACC_IDX); alice_wlt->refresh(); std::stringstream ss; - alice_wlt->dump_trunsfers(ss, false); + alice_wlt->dump_transfers(ss, false); LOG_PRINT_L0("check_normal_acceptance(" << release_instruction << "):" << ENDL << "Alice transfers: " << ENDL << ss.str()); uint64_t alice_balance = alice_wlt->balance(); uint64_t alice_balance_expected = m_alice_bob_start_amount - m_cpd.amount_a_pledge - m_cpd.amount_to_pay - TESTS_DEFAULT_FEE; @@ -1392,7 +1392,7 @@ bool escrow_incorrect_proposal_acceptance::check_incorrect_acceptance(currency:: alice_wlt->refresh(); uint64_t alice_balance = alice_wlt->balance(); std::stringstream ss; - alice_wlt->dump_trunsfers(ss, false); + alice_wlt->dump_transfers(ss, false); LOG_PRINT_L0("check_incorrect_acceptance(" << param << "):" << ENDL << "Alice balance: " << print_money(alice_balance) << ", transfers: " << ENDL << ss.str()); tools::escrow_contracts_container contracts; @@ -2110,7 +2110,7 @@ bool escrow_incorrect_cancel_proposal::check_normal_cancel_proposal(currency::co std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, ALICE_ACC_IDX); alice_wlt->refresh(); std::stringstream ss; - alice_wlt->dump_trunsfers(ss, false); + alice_wlt->dump_transfers(ss, false); LOG_PRINT_L0("check_normal_cancel_proposal:" << ENDL << "Alice transfers: " << ENDL << ss.str()); uint64_t alice_balance = alice_wlt->balance(); uint64_t alice_balance_expected = m_alice_bob_start_amount - m_cpd.amount_a_pledge - m_cpd.amount_to_pay - TESTS_DEFAULT_FEE - TESTS_DEFAULT_FEE; // one fee for escrow request, second - for cancel request @@ -2198,7 +2198,7 @@ bool escrow_incorrect_cancel_proposal::check_incorrect_cancel_proposal_internal( std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, ALICE_ACC_IDX); alice_wlt->refresh(); std::stringstream ss; - alice_wlt->dump_trunsfers(ss, false); + alice_wlt->dump_transfers(ss, false); LOG_PRINT_L0("Alice transfers: " << ENDL << ss.str()); uint64_t alice_balance = alice_wlt->balance(); uint64_t alice_balance_expected = m_alice_bob_start_amount - m_cpd.amount_a_pledge - m_cpd.amount_to_pay - TESTS_DEFAULT_FEE - TESTS_DEFAULT_FEE; // one fee for escrow request, second - for cancel request diff --git a/tests/core_tests/hard_fork_4.cpp b/tests/core_tests/hard_fork_4.cpp index dc5fc492..6729c498 100644 --- a/tests/core_tests/hard_fork_4.cpp +++ b/tests/core_tests/hard_fork_4.cpp @@ -227,8 +227,6 @@ bool hardfork_4_wallet_transfer_with_mandatory_mixins::generate(std::vector bool hardfork_4_pop_tx_from_global_index::c1(currency::core& c, size_t ev_index, const std::vector& events) { auto& bcs = c.get_blockchain_storage(); - bool r = false; //currency::outs_index_stat outs_stat{}; //bcs.get_outs_index_stat(outs_stat); // 24 - bad, 22 - good diff --git a/tests/core_tests/multiassets_test.cpp b/tests/core_tests/multiassets_test.cpp index a3f11010..b6c6a2b3 100644 --- a/tests/core_tests/multiassets_test.cpp +++ b/tests/core_tests/multiassets_test.cpp @@ -1469,8 +1469,6 @@ bool eth_signed_asset_basics::c1(currency::core& c, size_t ev_index, const std:: crypto::public_key asset_id = currency::null_pkey; miner_wlt->deploy_new_asset(adb, destinations, ft, asset_id); - const transaction& tx = ft.tx; - LOG_PRINT_L0("Deployed new asset: " << asset_id << ", tx_id: " << ft.tx_id); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count()); @@ -2122,7 +2120,7 @@ bool asset_current_and_total_supplies_comparative_constraints::generate(std::vec std::vector destinations{}; const auto& ado{m_ados_register.at(asset_position::gamma)}; crypto::secret_key one_time{}; - size_t hf_n = m_hardforks.get_the_most_recent_hardfork_id_for_height(get_block_height(blk_1r)); + //size_t hf_n = m_hardforks.get_the_most_recent_hardfork_id_for_height(get_block_height(blk_1r)); //fill_ado_version_based_onhardfork(ado, hf_n); //fill_adb_version_based_onhardfork(*ado.opt_descriptor, hf_n); @@ -2148,7 +2146,7 @@ bool asset_current_and_total_supplies_comparative_constraints::generate(std::vec std::vector destinations{}; crypto::secret_key one_time{}; const auto& ado{m_ados_register.at(asset_position::alpha)}; - size_t hf_n = m_hardforks.get_the_most_recent_hardfork_id_for_height(get_block_height(blk_2r)); + //size_t hf_n = m_hardforks.get_the_most_recent_hardfork_id_for_height(get_block_height(blk_2r)); //fill_ado_version_based_onhardfork(ado, hf_n); //fill_adb_version_based_onhardfork(*ado.opt_descriptor, hf_n); @@ -2173,7 +2171,7 @@ bool asset_current_and_total_supplies_comparative_constraints::generate(std::vec std::vector destinations{}; crypto::secret_key one_time{}; const auto& ado{m_ados_register.at(asset_position::beta)}; - size_t hf_n = m_hardforks.get_the_most_recent_hardfork_id_for_height(get_block_height(blk_2r)); + //size_t hf_n = m_hardforks.get_the_most_recent_hardfork_id_for_height(get_block_height(blk_2r)); //fill_ado_version_based_onhardfork(ado, hf_n); //fill_adb_version_based_onhardfork(*ado.opt_descriptor, hf_n); @@ -2210,7 +2208,6 @@ bool asset_current_and_total_supplies_comparative_constraints::generate(std::vec const auto& ado_register{m_ados_register.at(asset_position::beta)}; std::vector sources{}; std::vector destinations{}; - crypto::secret_key one_time{}; tx_source_entry source{}; finalize_tx_param ftp{}; finalized_tx ftx{}; @@ -2452,8 +2449,6 @@ bool several_asset_emit_burn_txs_in_pool::generate(std::vector bool several_asset_emit_burn_txs_in_pool::c1(currency::core& c, size_t ev_index, const std::vector& events) { - bool r = false; - std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, MINER_ACC_IDX); miner_wlt->refresh(); std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, ALICE_ACC_IDX); @@ -2614,7 +2609,6 @@ bool assets_transfer_with_smallest_amount::generate(std::vector= 4 // - bool r = false; uint64_t ts = test_core_time::get_time(); m_accounts.resize(TOTAL_ACCS_COUNT); account_base& miner_acc = m_accounts[MINER_ACC_IDX]; miner_acc.generate(); miner_acc.set_createtime(ts); @@ -2947,7 +2941,6 @@ bool asset_operations_and_chain_switching::generate(std::vector& events) { - bool r = false, stub = false;; std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, m_accounts[ALICE_ACC_IDX]); alice_wlt->refresh(); std::shared_ptr bob_wlt = init_playtime_test_wallet(events, c, m_accounts[BOB_ACC_IDX]); @@ -2962,7 +2955,7 @@ bool asset_operations_and_chain_switching::c1(currency::core& c, size_t ev_index bool asset_operations_and_chain_switching::c2(currency::core& c, size_t ev_index, const std::vector& events) { - bool r = false, stub = false;; + bool r = false; std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, m_accounts[ALICE_ACC_IDX]); alice_wlt->refresh(); std::shared_ptr bob_wlt = init_playtime_test_wallet(events, c, m_accounts[BOB_ACC_IDX]); @@ -2984,7 +2977,7 @@ bool asset_operations_and_chain_switching::c2(currency::core& c, size_t ev_index bool asset_operations_and_chain_switching::c3(currency::core& c, size_t ev_index, const std::vector& events) { - bool r = false, stub = false;; + bool r = false; std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, m_accounts[ALICE_ACC_IDX]); alice_wlt->refresh(); std::shared_ptr bob_wlt = init_playtime_test_wallet(events, c, m_accounts[BOB_ACC_IDX]); diff --git a/tests/core_tests/multisig_wallet_tests.cpp b/tests/core_tests/multisig_wallet_tests.cpp index 4c2dc2dc..55afd1f0 100644 --- a/tests/core_tests/multisig_wallet_tests.cpp +++ b/tests/core_tests/multisig_wallet_tests.cpp @@ -2576,7 +2576,7 @@ bool multisig_unconfirmed_transfer_and_multiple_scan_pool_calls::c1(currency::co alice_wlt->scan_tx_pool(stub); alice_wlt->get_transfers(transfers); - CHECK_AND_ASSERT_MES(transfers.size() == 0, false, "incorrect transfers size for Alice: " << transfers.size() << "\n" << alice_wlt->dump_trunsfers()); + CHECK_AND_ASSERT_MES(transfers.size() == 0, false, "incorrect transfers size for Alice: " << transfers.size() << "\n" << alice_wlt->dump_transfers()); alice_wlt->get_unconfirmed_transfers(unconfirmed_transfers); CHECK_AND_ASSERT_MES(unconfirmed_transfers.size() == 1, false, "incorrect unconfirmed transfers size for Alice: " << unconfirmed_transfers.size()); CHECK_AND_ASSERT_MES(alice_wlt->get_multisig_transfers().size() == 1, false, "incorrect multisig transfers size for Alice: " << alice_wlt->get_multisig_transfers().size()); @@ -2590,7 +2590,7 @@ bool multisig_unconfirmed_transfer_and_multiple_scan_pool_calls::c1(currency::co transfers.clear(); unconfirmed_transfers.clear(); alice_wlt->get_transfers(transfers); - CHECK_AND_ASSERT_MES(transfers.size() == 0, false, "incorrect transfers size for Alice: " << transfers.size() << "\n" << alice_wlt->dump_trunsfers()); + CHECK_AND_ASSERT_MES(transfers.size() == 0, false, "incorrect transfers size for Alice: " << transfers.size() << "\n" << alice_wlt->dump_transfers()); alice_wlt->get_unconfirmed_transfers(unconfirmed_transfers); CHECK_AND_ASSERT_MES(unconfirmed_transfers.size() == 1, false, "incorrect unconfirmed transfers size for Alice: " << unconfirmed_transfers.size()); CHECK_AND_ASSERT_MES(alice_wlt->get_multisig_transfers().size() == 1, false, "incorrect multisig transfers size for Alice: " << alice_wlt->get_multisig_transfers().size()); diff --git a/tests/core_tests/offers_test.cpp b/tests/core_tests/offers_test.cpp index 39c115ae..2812838b 100644 --- a/tests/core_tests/offers_test.cpp +++ b/tests/core_tests/offers_test.cpp @@ -658,7 +658,7 @@ bool offer_removing_and_selected_output::check_offers(currency::core& c, size_t std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, m_accounts[ALICE_ACC_IDX]); alice_wlt->refresh(); - LOG_PRINT_CYAN("Alice's transfers:" << ENDL << alice_wlt->dump_trunsfers(), LOG_LEVEL_0); + LOG_PRINT_CYAN("Alice's transfers:" << ENDL << alice_wlt->dump_transfers(), LOG_LEVEL_0); uint64_t alice_start_balance = alice_wlt->balance(); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); @@ -1354,7 +1354,7 @@ bool offer_cancellation_with_zero_fee::c1(currency::core& c, size_t ev_index, co bool r = false; std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, m_accounts[MINER_ACC_IDX]); miner_wlt->refresh(); - LOG_PRINT_CYAN("Miners's transfers:" << ENDL << miner_wlt->dump_trunsfers(), LOG_LEVEL_0); + LOG_PRINT_CYAN("Miners's transfers:" << ENDL << miner_wlt->dump_transfers(), LOG_LEVEL_0); uint64_t miner_start_balance = miner_wlt->balance(); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); diff --git a/tests/core_tests/pos_validation.cpp b/tests/core_tests/pos_validation.cpp index 28f66b6a..d02d4bbc 100644 --- a/tests/core_tests/pos_validation.cpp +++ b/tests/core_tests/pos_validation.cpp @@ -53,7 +53,7 @@ bool gen_pos_coinstake_already_spent::generate(std::vector& ev CREATE_TEST_WALLET(miner_wlt, miner, blk_0); REFRESH_TEST_WALLET_AT_GEN_TIME(events, miner_wlt, blk_2, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1); - LOG_PRINT_L0("miner's transfers:" << ENDL << miner_wlt->dump_trunsfers(false)); + LOG_PRINT_L0("miner's transfers:" << ENDL << miner_wlt->dump_transfers(false)); // Legend: (n) - PoW block, [m] - PoS block // 0 1 11 12 13 <-- blockchain height (assuming CURRENCY_MINED_MONEY_UNLOCK_WINDOW == 10) @@ -217,56 +217,110 @@ bool gen_pos_too_early_pos_block::configure_core(currency::core& c, size_t ev_in //------------------------------------------------------------------ -bool gen_pos_extra_nonce::generate(std::vector& events) const +bool gen_pos_extra_nonce::configure_core(currency::core& c, size_t ev_index, const std::vector&) { - uint64_t ts = time(NULL); - - GENERATE_ACCOUNT(miner); - GENERATE_ACCOUNT(alice); - MAKE_GENESIS_BLOCK(events, blk_0, miner, ts); - DO_CALLBACK(events, "configure_core"); - REWIND_BLOCKS(events, blk_0r, blk_0, miner); - - // Legend: (n) - PoW block, [m] - PoS block - // 0 10 11 <-- blockchain height (assuming CURRENCY_MINED_MONEY_UNLOCK_WINDOW == 10) - // (0 )--(0r)--[1 ] main chain - - // make a PoS block manually with incorrect timestamp - crypto::hash prev_id = get_block_hash(blk_0r); - size_t height = CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1; - currency::wide_difficulty_type diff = generator.get_difficulty_for_next_block(prev_id, false); - - const transaction& stake = blk_0.miner_tx; - crypto::public_key stake_tx_pub_key = get_tx_pub_key_from_extra(stake); - size_t stake_output_idx = 0; - size_t stake_output_gidx = 0; - uint64_t stake_output_amount =boost::get( stake.vout[stake_output_idx]).amount; - crypto::key_image stake_output_key_image; - keypair kp; - generate_key_image_helper(miner.get_keys(), stake_tx_pub_key, stake_output_idx, kp, stake_output_key_image); - crypto::public_key stake_output_pubkey = boost::get(boost::get(stake.vout[stake_output_idx]).target).key; - - pos_block_builder pb; - pb.step1_init_header(generator.get_hardforks(), height, prev_id); - pb.step2_set_txs(std::vector()); - pb.step3_build_stake_kernel(stake_output_amount, stake_output_gidx, stake_output_key_image, diff, prev_id, null_hash, blk_0r.timestamp); - - // use biggest possible extra nonce (255 bytes) + largest alias - currency::blobdata extra_nonce(255, 'x'); - //currency::extra_alias_entry alias = AUTO_VAL_INIT(alias); // TODO: this alias entry was ignored for a long time, now I commented it out, make sure it's okay -- sowle - //alias.m_alias = std::string(255, 'a'); - //alias.m_address = miner.get_keys().account_address; - //alias.m_text_comment = std::string(255, 'y'); - pb.step4_generate_coinbase_tx(generator.get_timestamps_median(prev_id), generator.get_already_generated_coins(blk_0r), alice.get_public_address(), extra_nonce, CURRENCY_MINER_TX_MAX_OUTS); - pb.step5_sign(stake_tx_pub_key, stake_output_idx, stake_output_pubkey, miner); - block blk_1 = pb.m_block; - - // EXPECTED: blk_1 is accepted - events.push_back(blk_1); - + currency::core_runtime_config pc = c.get_blockchain_storage().get_core_runtime_config(); + pc.min_coinstake_age = TESTS_POS_CONFIG_MIN_COINSTAKE_AGE; + pc.pos_minimum_heigh = TESTS_POS_CONFIG_POS_MINIMUM_HEIGH; + pc.hf4_minimum_mixins = 0; + pc.hard_forks = m_hardforks; + c.get_blockchain_storage().set_core_runtime_config(pc); return true; } +gen_pos_extra_nonce::gen_pos_extra_nonce() +{ + REGISTER_CALLBACK_METHOD(gen_pos_extra_nonce, configure_core); + REGISTER_CALLBACK_METHOD(gen_pos_extra_nonce, request_pow_template_with_nonce); + REGISTER_CALLBACK_METHOD(gen_pos_extra_nonce, check_pow_nonce); + REGISTER_CALLBACK_METHOD(gen_pos_extra_nonce, check_pos_nonce); +} + +// Test: verify custom extra_nonce in blocks and templates +/* + * Scenarios: + * 1. PoW block contains m_pow_nonce + * 2. PoS block contains m_pos_nonce + * 3. PoW mining template contains m_pow_template_nonce + */ +bool gen_pos_extra_nonce::generate(std::vector& events) const +{ + GENERATE_ACCOUNT(miner); + GENERATE_ACCOUNT(alice); + m_accounts.push_back(miner); + m_accounts.push_back(alice); + m_pow_nonce = currency::blobdata(254, 'w'); + m_pos_nonce = currency::blobdata(255, 's'); + m_pow_template_nonce = "POW_TEMPLATE123"; + + uint64_t ts = test_core_time::get_time(); + MAKE_GENESIS_BLOCK(events, blk_0, miner, ts); + DO_CALLBACK(events, "configure_core"); + MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner); + + block blk_2 = AUTO_VAL_INIT(blk_2); + ts = blk_2.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN / 2; // to increase main chain difficulty + bool r = generator.construct_block_manually(blk_2, blk_1, miner, test_generator::bf_timestamp, 0, 0, + ts, crypto::hash(), 1, transaction(), std::vector(), 0, m_pow_nonce); + CHECK_AND_ASSERT_MES(r, false, "construct_block_manually failed"); + events.push_back(blk_2); + + DO_CALLBACK(events, "check_pow_nonce"); + REWIND_BLOCKS(events, blk_0r, blk_2, miner); + + // setup params for PoS + const currency::transaction& stake = blk_2.miner_tx; + + currency::block new_pos_block; + bool ok = generate_pos_block_with_extra_nonce(generator, events, miner, alice, blk_0r, stake, m_pos_nonce, new_pos_block); + CHECK_AND_ASSERT_MES(ok, false, "generate_pos_block_with_extra_nonce failed"); + + events.push_back(new_pos_block); + DO_CALLBACK(events, "check_pos_nonce"); + DO_CALLBACK(events, "request_pow_template_with_nonce"); + return true; +} + +bool gen_pos_extra_nonce::request_pow_template_with_nonce(currency::core& c, size_t ev_index, const std::vector& events) +{ + block bl; + wide_difficulty_type diff; + uint64_t height; + bool ok = c.get_block_template(bl, m_accounts[0].get_public_address(), m_accounts[0].get_public_address(), diff, height, m_pow_template_nonce); + CHECK_AND_ASSERT_MES(ok, false, "get_block_template failed"); + CHECK_AND_ASSERT_MES(has_extra_nonce(bl, m_pow_template_nonce), false, "PoW extra_nonce not found"); + return true; +} + +bool gen_pos_extra_nonce::check_pow_nonce(currency::core& c, size_t ev_index, const std::vector& events) +{ + block top; + bool ok = c.get_blockchain_storage().get_top_block(top); + CHECK_AND_ASSERT_MES(ok, false, "get_top_block failed"); + CHECK_AND_ASSERT_MES(has_extra_nonce(top, m_pow_nonce), false, "PoW extra_nonce not found"); + return true; +} + +bool gen_pos_extra_nonce::check_pos_nonce(currency::core& c, size_t ev_index, const std::vector& events) +{ + block top; + bool ok = c.get_blockchain_storage().get_top_block(top); + CHECK_AND_ASSERT_MES(ok, false, "get_top_block failed"); + CHECK_AND_ASSERT_MES(has_extra_nonce(top, m_pos_nonce), false, "PoS extra_nonce not found"); + return true; +} + +bool gen_pos_extra_nonce::has_extra_nonce(currency::block& blk, const std::string& expected_nonce) +{ + if (auto const* ud = get_type_in_variant_container(blk.miner_tx.extra)) { + LOG_PRINT_L0("Found extra nonce: '" << ud->buff << "' expected: '" << expected_nonce << "'"); + return ud->buff == expected_nonce; + } + return false; +} + +//------------------------------------------------------------------ + gen_pos_min_allowed_height::gen_pos_min_allowed_height() { REGISTER_CALLBACK_METHOD(gen_pos_min_allowed_height, configure_core); @@ -728,7 +782,7 @@ void pos_wallet_minting_same_amount_diff_outs::dump_wallets_entries(const std::v LOG_PRINT2(LOG2_FILENAME, ENDL << ENDL << ENDL << ENDL << ENDL << ENDL, LOG_LEVEL_0); for (size_t i = 0; i < minting_wallets.size(); ++i) { - LOG_PRINT2(LOG2_FILENAME, "wallet #" << i << ":" << ENDL << minting_wallets[i].w->dump_trunsfers() << ENDL, LOG_LEVEL_0); + LOG_PRINT2(LOG2_FILENAME, "wallet #" << i << ":" << ENDL << minting_wallets[i].w->dump_transfers() << ENDL, LOG_LEVEL_0); } #undef LOG2_FILENAME } @@ -806,7 +860,7 @@ bool pos_wallet_big_block_test::c1(currency::core& c, size_t ev_index, const std miner_wlt->refresh(); miner_wlt->scan_tx_pool(stub_bool); - LOG_PRINT_L0("miner transfers:" << ENDL << miner_wlt->dump_trunsfers(false)); + LOG_PRINT_L0("miner transfers:" << ENDL << miner_wlt->dump_transfers(false)); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 2, false, "Incorrect number of txs in the pool: " << c.get_pool_transactions_count()); diff --git a/tests/core_tests/pos_validation.h b/tests/core_tests/pos_validation.h index 6b48deb3..a5363af9 100644 --- a/tests/core_tests/pos_validation.h +++ b/tests/core_tests/pos_validation.h @@ -71,7 +71,21 @@ struct gen_pos_too_early_pos_block : public pos_validation struct gen_pos_extra_nonce : public pos_validation { + gen_pos_extra_nonce(); + bool configure_core(currency::core& c, size_t, const std::vector& events); + bool request_pow_template_with_nonce(currency::core& c, size_t, const std::vector& events); + bool check_pos_nonce(currency::core& c, size_t, const std::vector& events); + bool check_pow_nonce(currency::core& c, size_t, const std::vector& events); bool generate(std::vector& events) const; + +private: + bool has_extra_nonce(currency::block& blk, const std::string& expected_nonce); + +private: + mutable currency::blobdata m_pow_nonce; + mutable currency::blobdata m_pos_nonce; + mutable currency::blobdata m_pow_template_nonce; + mutable std::vector m_accounts; }; struct gen_pos_min_allowed_height : public pos_validation diff --git a/tests/core_tests/random_helper.h b/tests/core_tests/random_helper.h index 61d49f80..f548e324 100644 --- a/tests/core_tests/random_helper.h +++ b/tests/core_tests/random_helper.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2019 Zano Project +// Copyright (c) 2014-2025 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -16,15 +16,15 @@ struct random_state_test_restorer { random_state_test_restorer() { - crypto::random_prng_get_state(&m_state, sizeof m_state); + crypto::random_prng_get_state_no_lock(&m_state, sizeof m_state); } ~random_state_test_restorer() { - crypto::random_prng_set_state(&m_state, sizeof m_state); + crypto::random_prng_set_state_no_lock(&m_state, sizeof m_state); } static void reset_random(uint64_t seed = 0) { - crypto::random_prng_initialize_with_seed(seed); + crypto::random_prng_initialize_with_seed_no_lock(seed); } private: uint8_t m_state[RANDOM_STATE_SIZE]; diff --git a/tests/core_tests/tx_validation.cpp b/tests/core_tests/tx_validation.cpp index 91274083..78bc9fa3 100644 --- a/tests/core_tests/tx_validation.cpp +++ b/tests/core_tests/tx_validation.cpp @@ -1651,6 +1651,7 @@ bool tx_version_against_hardfork::generate(std::vector& events { case ZANO_HARDFORK_04_ZARCANUM: tx_hardfork_id = 0; + [[fallthrough]]; case ZANO_HARDFORK_05: tx_version_good = TRANSACTION_VERSION_POST_HF4; tx_version_bad = TRANSACTION_VERSION_PRE_HF4; @@ -1836,6 +1837,7 @@ bool tx_pool_semantic_validation::generate(std::vector& events } CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true); + } // Two entries of the same type in extra. @@ -2567,7 +2569,6 @@ bool input_refers_to_incompatible_by_type_output::assert_zc_input_refers_bare_ou } { - bool all_inputs_have_explicit_native_asset_id{}; blockchain_storage::check_tx_inputs_context ctic{}; CHECK_AND_ASSERT_EQ(c.get_blockchain_storage().check_tx_input(tx, 0, boost::get(tx.vin.front()), get_transaction_hash(tx), ctic), false); @@ -2728,3 +2729,137 @@ bool tx_pool_validation_and_chain_switch::c1(currency::core& c, size_t ev_index, return true; } +// Сoinbase transactions must NOT allow the TX_FLAG_SIGNATURE_MODE_SEPARATE flag. +// Сhecks that setting this flag for coinbase fails, while a default coinbase (without the flag) succeeds. +bool tx_coinbase_separate_sig_flag::generate(std::vector& events) const +{ + GENERATE_ACCOUNT(miner); + + uint64_t ts = test_core_time::get_time(); + MAKE_GENESIS_BLOCK(events, blk_0, miner, ts); + DO_CALLBACK(events, "configure_core"); + MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner); + REWIND_BLOCKS_N(events, blk_1r, blk_1, miner, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1); + + block blk_2; + auto coinbase_default_cb = [](transaction& miner_tx, const keypair&) -> bool { return true; }; + auto coinbase_separate_cb = [](transaction& miner_tx, const keypair&) -> bool + { + set_tx_flags(miner_tx, get_tx_flags(miner_tx) | TX_FLAG_SIGNATURE_MODE_SEPARATE); + return true; + }; + + // сonstruct a block with the forbidden flag, should fail after hf4 + bool with_separate_flag = generator.construct_block_gentime_with_coinbase_cb(blk_1r, miner, coinbase_separate_cb, blk_2); + CHECK_AND_ASSERT_MES(with_separate_flag, false, "expected failure because TX_FLAG_SIGNATURE_MODE_SEPARATE is forbidden for coinbase after HF4"); + + DO_CALLBACK(events, "mark_invalid_block"); + events.push_back(blk_2); + + // construct a default coinbase block, should succeed + bool default_tx = generator.construct_block_gentime_with_coinbase_cb(blk_1r, miner, coinbase_default_cb, blk_2); + CHECK_AND_ASSERT_MES(default_tx, true, "default coinbase must succeed"); + + events.push_back(blk_2); + + return true; +} + +tx_input_mixins::tx_input_mixins() +{ + REGISTER_CALLBACK_METHOD(tx_input_mixins, configure_core); +} + +bool tx_input_mixins::configure_core(currency::core& c, size_t ev_index, const std::vector& events) +{ + currency::core_runtime_config pc = c.get_blockchain_storage().get_core_runtime_config(); + pc.min_coinstake_age = TESTS_POS_CONFIG_MIN_COINSTAKE_AGE; + pc.pos_minimum_heigh = TESTS_POS_CONFIG_POS_MINIMUM_HEIGH; + pc.hf4_minimum_mixins = 5; + pc.hard_forks.set_hardfork_height(1, 0); + pc.hard_forks.set_hardfork_height(2, 1); + pc.hard_forks.set_hardfork_height(3, 1); + pc.hard_forks.set_hardfork_height(4, 31); + c.get_blockchain_storage().set_core_runtime_config(pc); + return true; +} + +// Tests that post-HF4, legacy txin_to_key inputs accept any mixin count, while new txin_zc inputs enforce a >= 15 mixin minimum +// are in the same vin and work together +bool tx_input_mixins::generate(std::vector& events) const +{ + uint64_t ts = test_core_time::get_time(); + + GENERATE_ACCOUNT(miner_acc); + GENERATE_ACCOUNT(alice_acc); + GENERATE_ACCOUNT(bob_acc); + + m_hardforks.set_hardfork_height(1, 0); + m_hardforks.set_hardfork_height(2, 1); + m_hardforks.set_hardfork_height(3, 1); + m_hardforks.set_hardfork_height(4, 31); + MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, ts); + DO_CALLBACK(events, "configure_core"); + + // mine one block, then rewind enough to unlock initial coinbase funds + MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_acc); + REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW*2); + + // build tx_a_1: single txin_to_key output of 15 coins to Alice + transaction tx_a_1; + bool r = construct_tx_with_many_outputs(m_hardforks, events, blk_1r, miner_acc.get_keys(), + alice_acc.get_public_address(), MK_TEST_COINS(15), 1, TESTS_DEFAULT_FEE, tx_a_1); + CHECK_AND_ASSERT_MES(r, false, "construct_tx_with_many_outputs 1 failed"); + events.push_back(tx_a_1); + + // build tx_a_2: 16 outputs of 15 coins each to Bob for mixins + transaction tx_a_2; + r = construct_tx_with_many_outputs(m_hardforks, events, blk_1r, miner_acc.get_keys(), + bob_acc.get_public_address(), MK_TEST_COINS(15*16), 16, TESTS_DEFAULT_FEE, tx_a_2); + CHECK_AND_ASSERT_MES(r, false, "construct_tx_with_many_outputs 2 failed"); + events.push_back(tx_a_2); + + MAKE_NEXT_BLOCK_TX_LIST(events, blk_1r_1, blk_1r, miner_acc, std::list({ tx_a_1, tx_a_2 })); + + // mine a couple more blocks and rewind to generate new spendable outputs + MAKE_NEXT_BLOCK(events, blk_2, blk_1r_1, miner_acc); + REWIND_BLOCKS_N(events, blk_2r, blk_2, miner_acc, 4); + MAKE_NEXT_BLOCK(events, blk_3, blk_2r, miner_acc); + REWIND_BLOCKS_N(events, blk_4r, blk_3, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + // make sure HF4 is active at 31 height + DO_CALLBACK_PARAMS(events, "check_hardfork_active", size_t{ZANO_HARDFORK_04_ZARCANUM}); + + // build tx_b with zc hf4 input, send 15 coins from miner -> Alice + std::vector sources_b; + std::vector destinations_b; + CHECK_AND_ASSERT_MES(fill_tx_sources_and_destinations( + events, blk_4r, miner_acc, alice_acc, MK_TEST_COINS(15), TESTS_DEFAULT_FEE, 2, sources_b, destinations_b), + false, "fill_tx_sources_and_destinations failed"); + currency::transaction tx_b{}; + r = construct_tx(miner_acc.get_keys(), sources_b, destinations_b, events, this, tx_b); + CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); + events.push_back(tx_b); + + // mine tx_b and rewind to unlock its outputs + MAKE_NEXT_BLOCK_TX1(events, blk_5, blk_4r, miner_acc, tx_b); + REWIND_BLOCKS_N(events, blk_5r, blk_5, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + std::vector sources_c; + std::vector destinations_c; + // For input #0 use 5 mixins (old-style ring-CT allows any mixins) + // For input #1 use 16 mixins (new-style txin_zc enforces >=15 once HF4 is live) + mixins_per_input nmix_map = { {0, 5}, {1, 16} }; + CHECK_AND_ASSERT_MES(fill_tx_sources_and_destinations( + events, blk_5r, alice_acc, bob_acc, MK_TEST_COINS(29), TESTS_DEFAULT_FEE, 5, sources_c, destinations_c, + true, true, false, &nmix_map), false, "fill_tx_sources_and_destinations failed"); + currency::transaction tx_c{}; + r = construct_tx(alice_acc.get_keys(), sources_c, destinations_c, events, this, tx_c); + LOG_PRINT_GREEN("tx_c alice -> bob \n" << obj_to_json_str(tx_c), LOG_LEVEL_0); + CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); + events.push_back(tx_c); + + MAKE_NEXT_BLOCK_TX1(events, blk_6, blk_5r, miner_acc, tx_c); + + return true; +} diff --git a/tests/core_tests/tx_validation.h b/tests/core_tests/tx_validation.h index 3428d1eb..5a68d81d 100644 --- a/tests/core_tests/tx_validation.h +++ b/tests/core_tests/tx_validation.h @@ -183,3 +183,16 @@ struct tx_pool_validation_and_chain_switch : public wallet_test bool generate(std::vector& events) const; bool c1(currency::core& c, size_t ev_index, const std::vector& events); }; + +struct tx_coinbase_separate_sig_flag : public test_chain_unit_enchanced +{ + bool generate(std::vector& events) const; +}; + +struct tx_input_mixins: public test_chain_unit_enchanced +{ + tx_input_mixins(); + + bool configure_core(currency::core& c, size_t ev_index, const std::vector& events); + bool generate(std::vector& events) const; +}; diff --git a/tests/core_tests/wallet_rpc_tests.cpp b/tests/core_tests/wallet_rpc_tests.cpp index 76403881..1f8b4dc9 100644 --- a/tests/core_tests/wallet_rpc_tests.cpp +++ b/tests/core_tests/wallet_rpc_tests.cpp @@ -830,7 +830,6 @@ bool wallet_true_rpc_pos_mining::generate(std::vector& events) bool wallet_true_rpc_pos_mining::c1(currency::core& c, size_t ev_index, const std::vector& events) { - bool r = false; std::shared_ptr miner_wlt = init_playtime_test_wallet_with_true_http_rpc(events, c, MINER_ACC_IDX); std::shared_ptr alice_wlt = init_playtime_test_wallet_with_true_http_rpc(events, c, ALICE_ACC_IDX); @@ -1284,12 +1283,7 @@ bool wallet_rpc_cold_signing::generate(std::vector& events) co MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); DO_CALLBACK(events, "configure_core"); - REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); - - MAKE_TX(events, tx_0, miner_acc, alice_acc, MK_TEST_COINS(100), blk_0r); - MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_0); - - REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1); DO_CALLBACK(events, "c1"); @@ -1306,6 +1300,14 @@ bool make_cold_signing_transaction(tools::wallet_rpc_server& rpc_wo, tools::wall r = invoke_text_json_for_rpc(rpc_wo, "transfer", req_transfer, res_transfer); CHECK_AND_ASSERT_MES(r, false, "RPC 'transfer' failed"); + // (optional step) skip this reservation just for fun and then create a transaction once again + tools::wallet_public::COMMAND_CLEAR_UTXO_COLD_SIG_RESERVATION::request req_clear_csr{}; + tools::wallet_public::COMMAND_CLEAR_UTXO_COLD_SIG_RESERVATION::response res_clear_csr{}; + invoke_text_json_for_rpc(rpc_wo, "clear_utxo_cold_sig_reservation", req_clear_csr, res_clear_csr); + CHECK_AND_ASSERT_MES(res_clear_csr.affected_outputs.size() > 0, false, "no outputs were affected after clear_utxo_cold_sig_reservation"); + res_transfer = tools::wallet_public::COMMAND_RPC_TRANSFER::response{}; + r = invoke_text_json_for_rpc(rpc_wo, "transfer", req_transfer, res_transfer); + CHECK_AND_ASSERT_MES(r, false, "RPC 'transfer' failed"); // secure wallet with full keys set signs the transaction tools::wallet_public::COMMAND_SIGN_TRANSFER::request req_sign{}; @@ -1314,8 +1316,11 @@ bool make_cold_signing_transaction(tools::wallet_rpc_server& rpc_wo, tools::wall r = invoke_text_json_for_rpc(rpc, "sign_transfer", req_sign, res_sign); CHECK_AND_ASSERT_MES(r, false, "RPC 'sign_transfer' failed"); + // (optional step) make sure clear_utxo_cold_sig_reservation cannot be called in a full keys wallet + r = !invoke_text_json_for_rpc(rpc, "clear_utxo_cold_sig_reservation", req_clear_csr, res_clear_csr); + CHECK_AND_ASSERT_MES(r, false, "clear_utxo_cold_sig_reservation called in full key wallet"); - // watch only wallet submits it + // watch only wallet submits signed transaction tools::wallet_public::COMMAND_SUBMIT_TRANSFER::request req_submit{}; req_submit.tx_signed_hex = res_sign.tx_signed_hex; tools::wallet_public::COMMAND_SUBMIT_TRANSFER::response res_submit{}; @@ -1342,14 +1347,46 @@ bool wallet_rpc_cold_signing::c1(currency::core& c, size_t ev_index, const std:: miner_wlt->refresh(); bob_wlt->refresh(); - check_balance_via_wallet(*bob_wlt, "Bob", 0, 0, 0, 0, 0); + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*bob_wlt, "Bob", 0, 0, 0, 0, 0), false, ""); + // for post HF4 cases transfer some assets along with native coins + crypto::public_key deployed_asset_id{}; + size_t deployed_asset_decimal_point = 0; + const bool use_assets = c.get_blockchain_storage().is_hardfork_active(ZANO_HARDFORK_04_ZARCANUM); + if (use_assets) + { + // miner deploys new asset and sends 50 coins of it to Alice + tools::wallet_rpc_server miner_rpc(miner_wlt); + tools::wallet_public::COMMAND_ASSETS_DEPLOY::request req_deploy{}; + req_deploy.asset_descriptor.current_supply = 50; + req_deploy.asset_descriptor.decimal_point = deployed_asset_decimal_point; + req_deploy.asset_descriptor.full_name = "50 pounds per person"; + req_deploy.asset_descriptor.ticker = "50PPP"; + req_deploy.asset_descriptor.total_max_supply = 200; // for a family of four + req_deploy.destinations.emplace_back(tools::wallet_public::transfer_destination{50, m_accounts[ALICE_ACC_IDX].get_public_address_str(), null_pkey}); + req_deploy.do_not_split_destinations = true; + tools::wallet_public::COMMAND_ASSETS_DEPLOY::response res_deploy{}; + r = invoke_text_json_for_rpc(miner_rpc, "deploy_asset", req_deploy, res_deploy); + CHECK_AND_ASSERT_MES(r, false, "RPC 'deploy_asset' failed"); + deployed_asset_id = res_deploy.new_asset_id; + } + + // also, send some native coins + miner_wlt->transfer(MK_TEST_COINS(100), m_accounts[ALICE_ACC_IDX].get_public_address(), native_coin_asset_id); + + // confirm this tx and mine 10 block to unlock the coins + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == (use_assets ? 2 : 1), false, "enexpected pool txs count: " << c.get_pool_transactions_count()); + r = mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Tx pool is not empty: " << c.get_pool_transactions_count()); + + // Alice: prepare watch-only wallet account_base alice_acc_wo = m_accounts[ALICE_ACC_IDX]; alice_acc_wo.make_account_watch_only(); std::shared_ptr alice_wlt_wo = init_playtime_test_wallet(events, c, alice_acc_wo); alice_wlt_wo->refresh(); - check_balance_via_wallet(*alice_wlt_wo, "Alice (WO)", MK_TEST_COINS(100), 0, MK_TEST_COINS(100), 0, 0); + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt_wo, "Alice (WO)", MK_TEST_COINS(100), 0, MK_TEST_COINS(100), 0, 0), false, ""); // Alice: perform initial save-load to properly initialize internal wallet2 structures boost::filesystem::remove(epee::string_tools::cut_off_extension(m_wallet_filename) + L".outkey2ki"); @@ -1364,17 +1401,17 @@ bool wallet_rpc_cold_signing::c1(currency::core& c, size_t ev_index, const std:: tools::wallet_rpc_server alice_rpc(alice_wlt); std::shared_ptr alice_rpc_wo_ptr{ new tools::wallet_rpc_server(alice_wlt_wo) }; - // send a cold-signed transaction + // send a cold-signed transaction: Alice -> Bob; 50 test coins + 50 of the asset tools::wallet_public::COMMAND_RPC_TRANSFER::request req{}; - req.destinations.emplace_back(); - req.destinations.back().amount = MK_TEST_COINS(50); - req.destinations.back().address = m_accounts[BOB_ACC_IDX].get_public_address_str(); + req.destinations.emplace_back(tools::wallet_public::transfer_destination{MK_TEST_COINS(50), m_accounts[BOB_ACC_IDX].get_public_address_str(), native_coin_asset_id}); + if (use_assets) + req.destinations.emplace_back(tools::wallet_public::transfer_destination{50, m_accounts[BOB_ACC_IDX].get_public_address_str(), deployed_asset_id}); req.fee = TESTS_DEFAULT_FEE; req.mixin = 10; r = make_cold_signing_transaction(*alice_rpc_wo_ptr, alice_rpc, req); CHECK_AND_ASSERT_MES(r, false, "make_cold_signing_transaction failed"); - uint64_t bob_expected_balance = req.destinations.back().amount; + uint64_t bob_expected_balance = req.destinations.front().amount; // mine few blocks to unlock Alice's coins CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "unexpected pool txs count: " << c.get_pool_transactions_count()); @@ -1407,7 +1444,7 @@ bool wallet_rpc_cold_signing::c1(currency::core& c, size_t ev_index, const std:: CHECK_AND_ASSERT_EQ(blocks_fetched, 0); - // send a cold-signed transaction + // send a cold-signed transaction: Alice -> Bob; all rest test coins (should be 50 - 1 = 49) req = tools::wallet_public::COMMAND_RPC_TRANSFER::request{}; req.destinations.emplace_back(); req.destinations.back().amount = alice_expected_balance - TESTS_DEFAULT_FEE; @@ -1437,7 +1474,7 @@ bool wallet_rpc_cold_signing::c1(currency::core& c, size_t ev_index, const std:: CURRENCY_MINED_MONEY_UNLOCK_WINDOW, alice_expected_balance, 0, 0, 0), false, ""); - // send a cold-signed transaction + // send a cold-signed transaction: Alice -> Bob; all rest test coins (should be 7 - 1 = 6) req = tools::wallet_public::COMMAND_RPC_TRANSFER::request{}; req.destinations.emplace_back(); req.destinations.back().amount = alice_expected_balance - TESTS_DEFAULT_FEE; @@ -1461,11 +1498,167 @@ bool wallet_rpc_cold_signing::c1(currency::core& c, size_t ev_index, const std:: // check Alice's balance CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("after sending the second cold-signed tx:", "Alice", alice_wlt_wo, alice_expected_balance, true, CURRENCY_MINED_MONEY_UNLOCK_WINDOW, alice_expected_balance, 0, 0, 0), false, ""); + if (use_assets) + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt_wo.get(), "Alice", 0, 0, 0, 0, 0, deployed_asset_id, deployed_asset_decimal_point), false, ""); // finally, check Bob's balance - CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, bob_expected_balance, true, 3 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW, bob_expected_balance, 0, 0, 0), false, ""); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, bob_expected_balance, true, 4 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW, bob_expected_balance, 0, 0, 0), false, ""); + if (use_assets) + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*bob_wlt.get(), "Bob", 50, 0, 50, 0, 0, deployed_asset_id, deployed_asset_decimal_point), false, ""); + + + // Alice watch-only: reload the wallet, make sure the balance is still okay (zero) + alice_rpc_wo_ptr.reset(); + alice_wlt_wo->reset_password(m_wallet_password); + alice_wlt_wo->store(m_wallet_filename); + CHECK_AND_ASSERT_EQ(alice_wlt_wo.unique(), true); + alice_wlt_wo.reset(new tools::wallet2); + alice_wlt_wo->load(m_wallet_filename, m_wallet_password); + alice_wlt_wo->set_core_proxy(m_core_proxy); + alice_wlt_wo->set_core_runtime_config(c.get_blockchain_storage().get_core_runtime_config()); + set_wallet_options(alice_wlt_wo); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("after re-loading with zero balance:", "Alice", alice_wlt_wo, 0, true, 0, 0, 0, 0, 0), false, ""); + if (use_assets) + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt_wo.get(), "Alice", 0, 0, 0, 0, 0, deployed_asset_id, deployed_asset_decimal_point), false, ""); + + + // Alice: remove outkey2ki file with key images copies and restore the wallet from watch-only account + std::string alice_seed_phrase = m_accounts[ALICE_ACC_IDX].get_seed_phrase(""); + CHECK_AND_ASSERT_EQ(alice_wlt_wo.unique(), true); + alice_wlt_wo.reset(); + CHECK_AND_ASSERT_MES(boost::filesystem::remove(epee::string_tools::cut_off_extension(m_wallet_filename) + L".outkey2ki"), false, "boost::filesystem::remove failed"); + CHECK_AND_ASSERT_MES(boost::filesystem::remove(m_wallet_filename), false, "boost::filesystem::remove failed"); + + alice_wlt_wo = init_playtime_test_wallet(events, c, alice_acc_wo); + alice_wlt_wo->set_core_proxy(m_core_proxy); + alice_wlt_wo->set_core_runtime_config(c.get_blockchain_storage().get_core_runtime_config()); + set_wallet_options(alice_wlt_wo); + + // without key images Alice watch-only wallet is only able to detect incoming transfers and thus calculate incorrect balance + alice_expected_balance = MK_TEST_COINS(100 + 49 + 7); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("after sending the second cold-signed tx:", "Alice", alice_wlt_wo, alice_expected_balance, true, + 5 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1, alice_expected_balance, 0, 0, 0), false, ""); + if (use_assets) + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt_wo.get(), "Alice", 50, 0, 50, 0, 0, deployed_asset_id, deployed_asset_decimal_point), false, ""); + + // store this broken watch-only wallet into a file ... + alice_wlt_wo->reset_password(m_wallet_password); + alice_wlt_wo->store(m_wallet_filename); + CHECK_AND_ASSERT_EQ(alice_wlt_wo.unique(), true); + alice_wlt_wo.reset(); + + // ... and repair it using full key wallet + alice_wlt->restore_key_images_in_wo_wallet(m_wallet_filename, m_wallet_password); + + alice_wlt_wo.reset(new tools::wallet2); + alice_wlt_wo->load(m_wallet_filename, m_wallet_password); + alice_wlt_wo->set_core_proxy(m_core_proxy); + alice_wlt_wo->set_core_runtime_config(c.get_blockchain_storage().get_core_runtime_config()); + set_wallet_options(alice_wlt_wo); + + // re-check the balance, it should be zero now + alice_expected_balance = 0; + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("after sending the second cold-signed tx:", "Alice", alice_wlt_wo, alice_expected_balance, true, + 5 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1, alice_expected_balance, 0, 0, 0), false, ""); + if (use_assets) + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt_wo.get(), "Alice", 0, 0, 0, 0, 0, deployed_asset_id, deployed_asset_decimal_point), false, ""); return true; } + +//------------------------------------------------------------------------------ + +// TODO: work in progress -- sowle +wallet_rpc_multiple_receivers::wallet_rpc_multiple_receivers() +{ + REGISTER_CALLBACK_METHOD(wallet_rpc_multiple_receivers, c1); +} + +void wallet_rpc_multiple_receivers::set_wallet_options(std::shared_ptr w) +{ + set_playtime_test_wallet_options(w); + w->set_concise_mode(false); +} + +bool wallet_rpc_multiple_receivers::generate(std::vector& events) const +{ + m_accounts.resize(TOTAL_ACCS_COUNT); + account_base& miner_acc = m_accounts[MINER_ACC_IDX]; miner_acc.generate(); + account_base& alice_acc = m_accounts[ALICE_ACC_IDX]; alice_acc.generate(); + account_base& bob_acc = m_accounts[BOB_ACC_IDX]; bob_acc.generate(); + + MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); + DO_CALLBACK(events, "configure_core"); + + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1); + + DO_CALLBACK(events, "c1"); + + return true; +} + +bool wallet_rpc_multiple_receivers::c1(currency::core& c, size_t ev_index, const std::vector& events) +{ + bool r = false; + std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, MINER_ACC_IDX); + std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, ALICE_ACC_IDX); + std::shared_ptr bob_wlt = init_playtime_test_wallet(events, c, BOB_ACC_IDX); + + tools::wallet_rpc_server miner_rpc(miner_wlt); + tools::wallet_rpc_server alice_rpc(alice_wlt); + + miner_wlt->refresh(); + //bob_wlt->refresh(); + //check_balance_via_wallet(*bob_wlt, "Bob", 0, 0, 0, 0, 0); + + // for post HF4 cases transfer some assets along with native coins + crypto::public_key deployed_asset_id{}; + size_t deployed_asset_decimal_point = 0; + + // miner deploys new asset and sends 50 coins of it to Alice + tools::wallet_public::COMMAND_ASSETS_DEPLOY::request req_deploy{}; + req_deploy.asset_descriptor.current_supply = 50; + req_deploy.asset_descriptor.decimal_point = deployed_asset_decimal_point; + req_deploy.asset_descriptor.full_name = "50 pounds per person"; + req_deploy.asset_descriptor.ticker = "50PPP"; + req_deploy.asset_descriptor.total_max_supply = 200; // for a family of four + req_deploy.destinations.emplace_back(tools::wallet_public::transfer_destination{50, m_accounts[ALICE_ACC_IDX].get_public_address_str(), null_pkey}); + req_deploy.do_not_split_destinations = true; + tools::wallet_public::COMMAND_ASSETS_DEPLOY::response res_deploy{}; + r = invoke_text_json_for_rpc(miner_rpc, "deploy_asset", req_deploy, res_deploy); + CHECK_AND_ASSERT_MES(r, false, "RPC 'deploy_asset' failed"); + deployed_asset_id = res_deploy.new_asset_id; + + + // + tools::wallet_public::COMMAND_RPC_TRANSFER::request tr_req{}; + tools::wallet_public::COMMAND_RPC_TRANSFER::response tr_res{}; + tr_req.destinations.emplace_back(tools::wallet_public::transfer_destination{MK_TEST_COINS(80), m_accounts[ALICE_ACC_IDX].get_public_address_str()}); + tr_req.destinations.emplace_back(tools::wallet_public::transfer_destination{MK_TEST_COINS(90), m_accounts[BOB_ACC_IDX].get_public_address_str()}); + tr_req.fee = TESTS_DEFAULT_FEE; + tr_req.mixin = 0; + tr_req.hide_receiver = false; + tr_req.push_payer = true; + r = invoke_text_json_for_rpc(miner_rpc, "transfer", tr_req, tr_res); + CHECK_AND_ASSERT_MES(r, false, "RPC failed"); + + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 2, false, "enexpected pool txs count: " << c.get_pool_transactions_count()); + r = mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Tx pool is not empty: " << c.get_pool_transactions_count()); + + + // Alice: prepare watch-only wallet + alice_wlt->refresh(); + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", MK_TEST_COINS(80), 0, MK_TEST_COINS(80), 0, 0), false, ""); + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", req_deploy.asset_descriptor.current_supply, 0, req_deploy.asset_descriptor.current_supply, 0, 0, deployed_asset_id, deployed_asset_decimal_point), false, ""); + + bob_wlt->refresh(); + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*bob_wlt, "Bob", MK_TEST_COINS(90), 0, MK_TEST_COINS(90), 0, 0), false, ""); + + return true; +} + diff --git a/tests/core_tests/wallet_rpc_tests.h b/tests/core_tests/wallet_rpc_tests.h index 56855245..08e09017 100644 --- a/tests/core_tests/wallet_rpc_tests.h +++ b/tests/core_tests/wallet_rpc_tests.h @@ -74,3 +74,11 @@ struct wallet_rpc_cold_signing : public wallet_test mutable std::wstring m_wallet_filename = L"~coretests.wallet_rpc_cold_signing.file.tmp"; mutable std::string m_wallet_password = "ballerinacappuccina"; }; + +struct wallet_rpc_multiple_receivers : public wallet_test +{ + wallet_rpc_multiple_receivers(); + bool generate(std::vector& events) const; + bool c1(currency::core& c, size_t ev_index, const std::vector& events); + void set_wallet_options(std::shared_ptr w); +}; diff --git a/tests/core_tests/wallet_tests.cpp b/tests/core_tests/wallet_tests.cpp index 887f0581..b971c001 100644 --- a/tests/core_tests/wallet_tests.cpp +++ b/tests/core_tests/wallet_tests.cpp @@ -1443,14 +1443,14 @@ bool gen_wallet_transfers_and_chain_switch::generate(std::vector& events) const +bool gen_wallet_decrypted_payload_items::generate(std::vector& events) const { // Test outline // NOTE: All transactions are sending with same attachments: tx_payer, tx_comment and tx_message @@ -1508,16 +1508,18 @@ bool gen_wallet_decrypted_attachments::generate(std::vector& e // that feeling when you are a bit paranoid std::vector decrypted_attachments; currency::keypair k = currency::keypair::generate(); - transaction t = AUTO_VAL_INIT(t); - t.attachment = std::vector({ a_tx_payer, a_tx_comment, a_tx_message }); + transaction t{}; + t.attachment.push_back(a_tx_payer); + t.attachment.push_back(a_tx_message); + t.extra.push_back(a_tx_comment); add_tx_pub_key_to_extra(t, k.pub); add_attachments_info_to_extra(t.extra, t.attachment); - currency::encrypt_attachments(t, miner_acc.get_keys(), alice_acc.get_public_address(), k); + currency::encrypt_payload_items(t, miner_acc.get_keys(), alice_acc.get_public_address(), k); bool r = currency::decrypt_payload_items(true, t, alice_acc.get_keys(), decrypted_attachments); - CHECK_AND_ASSERT_MES(r, false, "encrypt_attachments + decrypt_attachments failed to work together"); + CHECK_AND_ASSERT_MES(r, false, "encrypt_payload_items + decrypt_attachments failed to work together"); } - MAKE_TX_ATTACH(events, tx_0, miner_acc, alice_acc, MK_TEST_COINS(10000), blk_0r, std::vector({ a_tx_payer, a_tx_comment, a_tx_message })); + MAKE_TX_EXTRA_ATTACH_FEE(events, tx_0, miner_acc, alice_acc, MK_TEST_COINS(10000), TESTS_DEFAULT_FEE, blk_0r, std::vector({ a_tx_comment }), std::vector({ a_tx_payer, a_tx_message })); MAKE_NEXT_BLOCK(events, blk_1, blk_0r, miner_acc); // don't put tx_0 into this block, the block is only necessary to trigger tx_pool scan on in wallet2::refresh() // wallet callback must be passed as shared_ptr, so to avoid deleting "this" construct shared_ptr with custom null-deleter @@ -1540,7 +1542,7 @@ bool gen_wallet_decrypted_attachments::generate(std::vector& e // Do the same in opposite direction: alice -> miner. Unlock money, received by Alice first. REWIND_BLOCKS_N(events, blk_2r, blk_2, miner_acc, WALLET_DEFAULT_TX_SPENDABLE_AGE); - MAKE_TX_ATTACH(events, tx_1, alice_acc, miner_acc, MK_TEST_COINS(100), blk_2r, std::vector({ a_tx_payer, a_tx_comment, a_tx_message })); + MAKE_TX_EXTRA_ATTACH_FEE(events, tx_1, alice_acc, miner_acc, MK_TEST_COINS(100), TESTS_DEFAULT_FEE, blk_2r, std::vector({ a_tx_comment }), std::vector({ a_tx_payer, a_tx_message })); MAKE_NEXT_BLOCK(events, blk_3, blk_2r, miner_acc); // don't put tx_1 into this block, the block is only necessary to trigger tx_pool scan on in wallet2::refresh() // Spend tx was not sent via Alice wallet instance, so wallet can't obtain destination address and pass it to callback (although, it decrypts attachments & extra) @@ -1573,7 +1575,7 @@ bool gen_wallet_decrypted_attachments::generate(std::vector& e m_comment_to_be_checked = a_tx_comment.comment; m_address_to_be_checked = get_account_address_as_str(bob_acc.get_public_address()); // note, that a_tx_payer is NOT refering to Bob's account, but in the callback we should get correct sender addr m_on_transfer2_called = false; - MAKE_TEST_WALLET_TX_ATTACH(events, tx_2, alice_wlt, MK_TEST_COINS(2000), bob_acc, std::vector({ a_tx_payer, a_tx_comment, a_tx_message })); + MAKE_TEST_WALLET_TX_EXTRA_ATTACH(events, tx_2, alice_wlt, MK_TEST_COINS(2000), bob_acc, std::vector({ a_tx_comment }), std::vector({ a_tx_payer, a_tx_message })); CHECK_AND_ASSERT_MES(m_on_transfer2_called, false, "on_transfer2() was not called (5)"); MAKE_NEXT_BLOCK(events, blk_5, blk_4r, miner_acc); // don't put tx_2 into this block, the block is only necessary to trigger tx_pool scan on in wallet2::refresh() @@ -1595,7 +1597,7 @@ bool gen_wallet_decrypted_attachments::generate(std::vector& e REFRESH_TEST_WALLET_AT_GEN_TIME(events, bob_wlt, blk_6r, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 2 + WALLET_DEFAULT_TX_SPENDABLE_AGE + 2 + WALLET_DEFAULT_TX_SPENDABLE_AGE + 2 + WALLET_DEFAULT_TX_SPENDABLE_AGE); a_tx_payer.acc_addr = bob_acc.get_public_address(); // here we specify correct payer address, as it will not be masked by wallet - MAKE_TEST_WALLET_TX_ATTACH(events, tx_3, bob_wlt, MK_TEST_COINS(200), alice_acc, std::vector({ a_tx_payer, a_tx_comment, a_tx_message })); + MAKE_TEST_WALLET_TX_EXTRA_ATTACH(events, tx_3, bob_wlt, MK_TEST_COINS(200), alice_acc, std::vector({ a_tx_comment }), std::vector({ a_tx_payer, a_tx_message })); MAKE_NEXT_BLOCK(events, blk_7, blk_6r, miner_acc); // don't put tx_3 into this block, the block is only necessary to trigger tx_pool scan on in wallet2::refresh() @@ -1612,7 +1614,7 @@ bool gen_wallet_decrypted_attachments::generate(std::vector& e return true; } -void gen_wallet_decrypted_attachments::on_transfer2(const tools::wallet_public::wallet_transfer_info& wti, const std::list& balances, uint64_t total_mined) +void gen_wallet_decrypted_payload_items::on_transfer2(const tools::wallet_public::wallet_transfer_info& wti, const std::list& balances, uint64_t total_mined) { m_on_transfer2_called = true; //try { @@ -2986,7 +2988,7 @@ bool mined_balance_wallet_test::c1(currency::core& c, size_t ev_index, const std miner_wlt->refresh(); std::stringstream ss; - miner_wlt->dump_trunsfers(ss, false); + miner_wlt->dump_transfers(ss, false); LOG_PRINT_CYAN("miner transfers: " << ENDL << ss.str(), LOG_LEVEL_0); CHECK_AND_ASSERT_MES(check_balance_via_wallet(*miner_wlt.get(), "miner", miner_mined_money, miner_mined_money), false, "wrong balance"); @@ -3798,7 +3800,6 @@ bool wallet_and_sweep_below::generate(std::vector& events) con bool wallet_and_sweep_below::c1(currency::core& c, size_t ev_index, const std::vector& events) { - bool r = false; std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, MINER_ACC_IDX); uint64_t miner_balance = (3 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW - 1) * COIN; @@ -3959,14 +3960,12 @@ bool wallet_reorganize_and_trim_test::c1(currency::core& c, size_t ev_index, con mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, 2); mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, WALLET_REORGANIZE_AND_TRIM_TEST_REORG_SIZE); - uint64_t h1 = c.get_blockchain_storage().get_top_block_height(); miner_wlt->refresh(); uint64_t unlocked = 0; uint64_t total = miner_wlt->balance(unlocked); c.get_blockchain_storage().truncate_blockchain(c.get_blockchain_storage().get_top_block_height() - (WALLET_REORGANIZE_AND_TRIM_TEST_REORG_SIZE-1)); mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, 10); - uint64_t h2 = c.get_blockchain_storage().get_top_block_height(); miner_wlt->refresh(); uint64_t unlocked2 = 0; uint64_t total2 = miner_wlt->balance(unlocked2); diff --git a/tests/core_tests/wallet_tests.h b/tests/core_tests/wallet_tests.h index ab7a109c..5c105b7b 100644 --- a/tests/core_tests/wallet_tests.h +++ b/tests/core_tests/wallet_tests.h @@ -104,9 +104,9 @@ struct gen_wallet_transfers_and_chain_switch : public wallet_test bool generate(std::vector& events) const; }; -struct gen_wallet_decrypted_attachments : public wallet_test, virtual public tools::i_wallet2_callback +struct gen_wallet_decrypted_payload_items : public wallet_test, virtual public tools::i_wallet2_callback { - gen_wallet_decrypted_attachments(); + gen_wallet_decrypted_payload_items(); bool generate(std::vector& events) const; // intrface tools::i_wallet2_callback diff --git a/tests/crypto/crypto.cpp b/tests/crypto/crypto.cpp index cbc5879c..381960a5 100644 --- a/tests/crypto/crypto.cpp +++ b/tests/crypto/crypto.cpp @@ -11,7 +11,7 @@ bool check_scalar(const crypto::ec_scalar &scalar) { } void random_scalar(crypto::ec_scalar &res) { - crypto::random_scalar(res); + crypto::random_scalar_no_lock(res); } void hash_to_scalar(const void *data, std::size_t length, crypto::ec_scalar &res) { diff --git a/tests/functional_tests/crypto_tests.cpp b/tests/functional_tests/crypto_tests.cpp index 599922f2..6ff25a18 100644 --- a/tests/functional_tests/crypto_tests.cpp +++ b/tests/functional_tests/crypto_tests.cpp @@ -690,12 +690,12 @@ size_t find_pos_hash(const boost::multiprecision::uint256_t& L_div_D, const curr TEST(crypto, pos) { //scalar_t D = 10000000000000001u; - scalar_t D = 13042196742415129u; // prime number - currency::wide_difficulty_type D_w = D.as_boost_mp_type().convert_to(); - uint64_t amount = 1000000000000; - size_t count_old = 0; - size_t count_new = 0; - size_t count_3 = 0; + [[maybe_unused]] scalar_t D = 13042196742415129u; // prime number + [[maybe_unused]] currency::wide_difficulty_type D_w = D.as_boost_mp_type().convert_to(); + [[maybe_unused]] uint64_t amount = 1000000000000; + [[maybe_unused]] size_t count_old = 0; + [[maybe_unused]] size_t count_new = 0; + [[maybe_unused]] size_t count_3 = 0; scalar_t x; x.make_random(); @@ -704,14 +704,14 @@ TEST(crypto, pos) const boost::multiprecision::uint256_t c_L_w = c_scalar_L.as_boost_mp_type(); const boost::multiprecision::uint256_t c_L_div_D_w = c_L_w / D_w; - boost::multiprecision::uint512_t h_tres = c_L_div_D_w * amount; + [[maybe_unused]] boost::multiprecision::uint512_t h_tres = c_L_div_D_w * amount; - currency::wide_difficulty_type final_diff = D_w / amount; + [[maybe_unused]] currency::wide_difficulty_type final_diff = D_w / amount; - boost::multiprecision::uint512_t Lv = boost::multiprecision::uint512_t(c_L_w) * amount; + [[maybe_unused]] boost::multiprecision::uint512_t Lv = boost::multiprecision::uint512_t(c_L_w) * amount; constexpr uint64_t c_coin = 1000000000000; - const uint64_t amounts[] = { + [[maybe_unused]] const uint64_t amounts[] = { c_coin / 100, c_coin / 50, c_coin / 20, @@ -738,9 +738,9 @@ TEST(crypto, pos) c_coin * 500000 }; - uint64_t kernel = 0; - scalar_t d0 = 0; - uint64_t d1 = 0; + [[maybe_unused]] uint64_t kernel = 0; + [[maybe_unused]] scalar_t d0 = 0; + [[maybe_unused]] uint64_t d1 = 0; /* @@ -1064,7 +1064,8 @@ TEST(crypto, hp) TEST(perf, cn_fast_hash) { //return true; - const crypto::hash h_initial = *(crypto::hash*)(&scalar_t::random()); + scalar_t s_rnd = scalar_t::random(); + const crypto::hash h_initial = *(crypto::hash*)(&s_rnd); std::vector> test_data; test_data.push_back(std::vector(32, 0)); @@ -1631,8 +1632,8 @@ TEST(crypto, schnorr_sig) { public_key invalid_pk = parse_tpod_from_hex_string("0000000000000000000000000000000000000000000000000000000000000001"); ASSERT_FALSE(check_key(invalid_pk)); - - hash m = *(crypto::hash*)(&scalar_t::random()); + scalar_t s_rnd = scalar_t::random(); + hash m = *(crypto::hash*)(&s_rnd); for(size_t i = 0; i < 100; ++i) { generic_schnorr_sig_s ss{}; diff --git a/tests/functional_tests/crypto_tests_clsag.h b/tests/functional_tests/crypto_tests_clsag.h index 96897e05..e9273b50 100644 --- a/tests/functional_tests/crypto_tests_clsag.h +++ b/tests/functional_tests/crypto_tests_clsag.h @@ -219,8 +219,6 @@ TEST(clsag, bad_sig) TEST(clsag, sig_difference) { - int cmp_res = 0; - clsag_gg_sig_check_t cc0, cc1; cc0.prepare_random_data(8); cc1 = cc0; // get the same input data diff --git a/tests/functional_tests/crypto_tests_performance.h b/tests/functional_tests/crypto_tests_performance.h index fff2f461..c1530268 100644 --- a/tests/functional_tests/crypto_tests_performance.h +++ b/tests/functional_tests/crypto_tests_performance.h @@ -344,7 +344,8 @@ TEST(perf, primitives) p = p + p; } ge_p3 Q; - ge_scalarmult_base(&Q, &scalar_t::random().m_s[0]); + scalar_t s_rnd = scalar_t::random(); + ge_scalarmult_base(&Q, &s_rnd.m_s[0]); std::vector results(rnd_indecies.size()); t.start(); @@ -1109,8 +1110,6 @@ struct pme_runner_t : public pme_runner_i TEST(perf, msm) { - bool r = false; - std::deque> runners; //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v1> >("Zarcanum, BPPE, 128", 1)); //runners.emplace_front(std::make_unique< pme_runner_t<128, bpp_crypto_trait_Zarcanum, mes_msm_and_check_zero_pippenger_v1> >("Zarcanum, BPPE, 128", 2)); diff --git a/tests/functional_tests/crypto_tests_range_proofs.h b/tests/functional_tests/crypto_tests_range_proofs.h index 527e84d4..cb68deee 100644 --- a/tests/functional_tests/crypto_tests_range_proofs.h +++ b/tests/functional_tests/crypto_tests_range_proofs.h @@ -352,7 +352,7 @@ TEST(bppe, power_128) std::vector& commitments = commitments_vector.back(); scalar_vec_t masks, masks2; - for(auto& el: values) + for (size_t i = 0; i < values.size(); ++i) { masks.emplace_back(scalar_t::random()); masks2.emplace_back(scalar_t::random()); diff --git a/tests/functional_tests/difficulty_analysis.cpp b/tests/functional_tests/difficulty_analysis.cpp index 3df52f21..d68d2583 100644 --- a/tests/functional_tests/difficulty_analysis.cpp +++ b/tests/functional_tests/difficulty_analysis.cpp @@ -236,7 +236,6 @@ void perform_simulation_for_function(const std::map& timesta std::vector backward_cumul_difficulties; backward_cumul_difficulties.reserve(BBR_DIFFICULTY_WINDOW); std::copy(cumul_difficulties.rbegin(), cumul_difficulties.rbegin() + BBR_DIFFICULTY_WINDOW - 1, std::back_inserter(backward_cumul_difficulties)); - uint64_t ts = timestamps.back(); curren_difficulty = cb(backward_timestamps, backward_cumul_difficulties, BBR_DIFFICULTY_TARGET); } cumul_difficulties.push_back(cumul_difficulties.back() + curren_difficulty); @@ -357,7 +356,7 @@ void hash_rate_analysis(const std::string& path) uint64_t curren_hashrate = 0; uint64_t step = 10; uint64_t hash_rate_range = 10; - uint64_t second_windowf_or_hashrate = 20*60; + // uint64_t second_windowf_or_hashrate = 20*60; for (size_t i = hash_rate_range; i != blocks.size(); i++) { diff --git a/tests/performance_tests/api_test.cpp b/tests/performance_tests/api_test.cpp index 47fa456e..fb7678f2 100644 --- a/tests/performance_tests/api_test.cpp +++ b/tests/performance_tests/api_test.cpp @@ -19,7 +19,7 @@ int test_get_rand_outs() m_core_proxy->set_connection_addr("127.0.0.1:11211"); m_core_proxy->check_connection(); - bool r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS(req, rsp); + m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS(req, rsp); return 0; } diff --git a/tests/performance_tests/chacha_stream_performance_test.cpp b/tests/performance_tests/chacha_stream_performance_test.cpp index fdae7e36..8a331b2b 100644 --- a/tests/performance_tests/chacha_stream_performance_test.cpp +++ b/tests/performance_tests/chacha_stream_performance_test.cpp @@ -41,9 +41,8 @@ bool perform_crypt_stream_iteration(const std::list(new tools::db::lmdb_db_backend), m_rw_lock); diff --git a/tests/unit_tests/db_tests.cpp b/tests/unit_tests/db_tests.cpp index 61a0ae19..4eb7a2d5 100644 --- a/tests/unit_tests/db_tests.cpp +++ b/tests/unit_tests/db_tests.cpp @@ -362,8 +362,8 @@ namespace db_test void multithread_test_1() { char prng_state[200] = {}; - crypto::random_prng_get_state(prng_state, sizeof prng_state); // store current RPNG state - crypto::random_prng_initialize_with_seed(0); // this mades this test deterministic + crypto::random_prng_get_state_no_lock(prng_state, sizeof prng_state); // store current RPNG state + crypto::random_prng_initialize_with_seed_no_lock(0); // this mades this test deterministic bool result = false; try @@ -377,7 +377,7 @@ namespace db_test } // restore PRNG state to keep other tests unaffected - crypto::random_prng_set_state(prng_state, sizeof prng_state); + crypto::random_prng_set_state_no_lock(prng_state, sizeof prng_state); ASSERT_TRUE(result); } diff --git a/tests/unit_tests/decoy_selection.cpp b/tests/unit_tests/decoy_selection.cpp index 19c2d178..b050837f 100644 --- a/tests/unit_tests/decoy_selection.cpp +++ b/tests/unit_tests/decoy_selection.cpp @@ -8,7 +8,7 @@ TEST(decoy_selection_test, decoy_selection_test) { - const uint64_t test_scale_size = 20000; + // const uint64_t test_scale_size = 20000; decoy_selection_generator dsg; dsg.init(100); std::map hits; diff --git a/tests/unit_tests/multiassets_test.cpp b/tests/unit_tests/multiassets_test.cpp index 533d8826..16b3fa7a 100644 --- a/tests/unit_tests/multiassets_test.cpp +++ b/tests/unit_tests/multiassets_test.cpp @@ -443,8 +443,6 @@ TEST(multiassets, native_serialization_get_or_calculate_asset_id_undefined) '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x97', '\x9e', '\xb7', '\x06', '\xac', '\xe2', '\xeb', '\x83', '\xf9', '\x12', 'V', 'X', '\xb2', '?', '\xb3', 'R', ' ', '\x84', '\x80', '\xcb', ';', '\x90', '\xc4', '>', '-', '\xf0', '\xd2', '\x98', '\xf9', 'u', 'N', '\xbc'}; - crypto::point_t expected_point_asset_id{}; - crypto::public_key expected_asset_id{}; crypto::point_t calculated_point_asset_id{}; crypto::public_key calculated_asset_id{}; const std::optional ado{deserialize(serialization_method::native, serialized_ado)}; diff --git a/tests/unit_tests/pod_array_file_container.cpp b/tests/unit_tests/pod_array_file_container.cpp new file mode 100644 index 00000000..ba4615aa --- /dev/null +++ b/tests/unit_tests/pod_array_file_container.cpp @@ -0,0 +1,364 @@ +// Copyright (c) 2025 Zano Project +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "epee/include/include_base_utils.h" +#include "crypto/crypto.h" +#include "crypto/crypto-sugar.h" +#include "gtest/gtest.h" +#include +#include +#include +#include + +#include "common/pod_array_file_container.h" + +// helper: returns a unique temp file path +static boost::filesystem::path make_temp_file() +{ + return boost::filesystem::temp_directory_path() / boost::filesystem::unique_path("pod_test_%%%%-%%%%.bin"); +} + +//====================================================================== +// typed test fixture for pod_array_file_container +// container = alias for pod_array_file_container +// tmp_path = temp file for test +// SetUp() = generate temp path (SetUp from ::testing::Test) +// TearDown() = remove temp file (TearDown from ::testing::Test) +//====================================================================== + +template +class pod_array_file_typed_test : public ::testing::Test +{ +protected: + using container = tools::pod_array_file_container; + boost::filesystem::path tmp_path; + + void SetUp() override + { + tmp_path = make_temp_file(); + } + + void TearDown() override + { + if (boost::filesystem::exists(tmp_path)) + boost::filesystem::remove(tmp_path); + } +}; + +// list of integral types to test +using integral_types = ::testing::Types< + int8_t, uint8_t, + int16_t, uint16_t, + int32_t, uint32_t, + int64_t, uint64_t +>; +// register typed tests +TYPED_TEST_CASE(pod_array_file_typed_test, integral_types); + +// push back and get items: +// write values [-1,0,1] and verify they are read back correctly +TYPED_TEST(pod_array_file_typed_test, push_back_and_get_items) +{ + typename TestFixture::container c; + ASSERT_TRUE(c.open(this->tmp_path.wstring(), true)); + + std::vector values = + { + static_cast(-1), + 0, + static_cast(1) + }; + + for (auto v : values) + ASSERT_TRUE(c.push_back(v)); + + EXPECT_EQ(c.size(), values.size()); + + TypeParam read_value; + for (size_t i = 0; i < values.size(); ++i) + { + ASSERT_TRUE(c.get_item(i, read_value)); + EXPECT_EQ(read_value, values[i]); + } +} + +// ensure get_item returns false for index >= size +TYPED_TEST(pod_array_file_typed_test, get_item_out_of_range) +{ + typename TestFixture::container c; + ASSERT_TRUE(c.open(this->tmp_path.wstring(), true)); + + TypeParam dummy; + EXPECT_FALSE(c.get_item(0, dummy)); + EXPECT_FALSE(c.get_item(100, dummy)); +} + +// -------------------------------------------------------------------- + +typedef uint32_t pod_t; +typedef tools::pod_array_file_container pod_container; + +// open fails if not exist without create: +// open() false when file missing and create_if_not_exist=false +TEST(pod_array_file_container, open_fails_if_not_exist_without_create) +{ + auto path = make_temp_file(); + pod_container c; + std::string reason; + bool opened = c.open(path.wstring(), false, nullptr, &reason); + EXPECT_FALSE(opened); + EXPECT_TRUE(reason.find("not exist") != std::string::npos); +} + +// open creates file when not exist: +// open() with create flag creates empty file and size=0 +TEST(pod_array_file_container, open_creates_file_when_not_exist) +{ + auto path = make_temp_file(); + pod_container c; + bool corrupted = true; + ASSERT_TRUE(c.open(path.wstring(), true, &corrupted, nullptr)); + EXPECT_FALSE(corrupted); + EXPECT_EQ(c.size(), 0u); + c.close(); + EXPECT_TRUE(boost::filesystem::exists(path)); +} + +// -------------------------------------------------------------------- + +// POD struct with fixed-size fields +struct test_struct +{ + crypto::public_key pubkey; + crypto::key_image key; +}; + +typedef tools::pod_array_file_container struct_container; + +// push back and get items struct: +// write two test_struct and verify memory equality +TEST(pod_array_file_container_struct, push_back_and_get_items_struct) +{ + auto path = make_temp_file(); + struct_container c; + ASSERT_TRUE(c.open(path.wstring(), true)); + + test_struct a1{}, a2{}; + + // use random values for pubkey and key + a1.pubkey = (crypto::scalar_t::random() * crypto::c_point_G).to_public_key(); + a1.key = (crypto::scalar_t::random() * crypto::c_point_G).to_key_image(); + a2.pubkey = (crypto::scalar_t::random() * crypto::c_point_G).to_public_key(); + a2.key = (crypto::scalar_t::random() * crypto::c_point_G).to_key_image(); + + ASSERT_TRUE(c.push_back(a1)); + ASSERT_TRUE(c.push_back(a2)); + EXPECT_EQ(c.size(), 2u); + + test_struct got; + ASSERT_TRUE(c.get_item(0, got)); + EXPECT_EQ(got.pubkey, a1.pubkey); + EXPECT_EQ(got.key, a1.key); + ASSERT_TRUE(c.get_item(1, got)); + EXPECT_EQ(got.pubkey, a2.pubkey); + EXPECT_EQ(got.key, a2.key); +} + +// get item out of range struct: +// get_item false when no items written +TEST(pod_array_file_container_struct, get_item_out_of_range_struct) +{ + auto path = make_temp_file(); + struct_container c; + ASSERT_TRUE(c.open(path.wstring(), true)); + test_struct dummy; + EXPECT_FALSE(c.get_item(0, dummy)); +} + +// corrupted file truncation struct: +// simulate corrupted file tail and check truncation flag and size +TEST(pod_array_file_container_struct, corrupted_file_truncation_struct) +{ + auto path = make_temp_file(); + { + // write a valid test_struct followed by garbage data + boost::filesystem::ofstream out(path, std::ios::binary | std::ios::out); + test_struct tmp{}; + std::fill(std::begin(tmp.pubkey.data), std::end(tmp.pubkey.data), 'X'); + std::fill(std::begin(tmp.key.data), std::end(tmp.key.data), 'Y'); + out.write(reinterpret_cast(&tmp), sizeof(tmp)); + const char garbage[5] = {1,2,3,4,5}; + out.write(garbage, sizeof(garbage)); + } + + struct_container c; + bool corrupted = false; + std::string reason; + ASSERT_TRUE(c.open(path.wstring(), false, &corrupted, &reason)); + EXPECT_TRUE(corrupted); + EXPECT_EQ(c.size(), 1u); + + test_struct got; + ASSERT_TRUE(c.get_item(0, got)); + + for (size_t i = 0; i < sizeof(got.pubkey.data); ++i) + EXPECT_EQ(got.pubkey.data[i], 'X'); + for (size_t i = 0; i < sizeof(got.key.data); ++i) + EXPECT_EQ(got.key.data[i], 'Y'); + + EXPECT_TRUE(reason.find("truncated") != std::string::npos); +} + +// persistence between opens struct: +// write multiple structs, reopen file, verify data persists +TEST(pod_array_file_container_struct, persistence_between_opens_struct) +{ + auto path = make_temp_file(); + { + // write 3 test_struct with different pubkey and key values + struct_container c; + ASSERT_TRUE(c.open(path.wstring(), true)); + for (int i = 0; i < 3; ++i) + { + test_struct tmp{}; + tmp.pubkey.data[0] = '0' + i; + tmp.key.data[0] = 'A' + i; + ASSERT_TRUE(c.push_back(tmp)); + } + EXPECT_EQ(c.size(), 3u); + } + + // reopen and verify data + struct_container c2; + ASSERT_TRUE(c2.open(path.wstring(), false)); + EXPECT_EQ(c2.size(), 3u); + test_struct got; + for (int i = 0; i < 3; ++i) + { + ASSERT_TRUE(c2.get_item(i, got)); + EXPECT_EQ(got.pubkey.data[0], '0' + i); + EXPECT_EQ(got.key.data[0], 'A' + i); + } +} + +// size bytes and size: +// check size_bytes() matches raw byte count and size() element count +TEST(pod_array_file_container, size_bytes_and_size) +{ + auto path = make_temp_file(); + pod_container c; + ASSERT_TRUE(c.open(path.wstring(), true)); + EXPECT_EQ(c.size_bytes(), 0u); + EXPECT_EQ(c.size(), 0u); + // push one element + pod_t value = 42; + ASSERT_TRUE(c.push_back(value)); + EXPECT_EQ(c.size_bytes(), sizeof(pod_t)); + EXPECT_EQ(c.size(), 1u); +} + +// operations after close: +// ensure push_back and get_item fail after close() +TEST(pod_array_file_container, operations_after_close) +{ + auto path = make_temp_file(); + pod_container c; + ASSERT_TRUE(c.open(path.wstring(), true)); + ASSERT_TRUE(c.push_back(123u)); + c.close(); + // after close, operations should return false + EXPECT_FALSE(c.push_back(456u)); + pod_t dummy = 0; + EXPECT_FALSE(c.get_item(0, dummy)); +} + +// open fails if cannot open (directory): +// attempt to open a directory path should fail with "file could not be opened" +TEST(pod_array_file_container, open_fails_if_cannot_open) +{ + // create a directory instead of a file + auto dir_path = make_temp_file(); + boost::filesystem::create_directory(dir_path); + pod_container c; + std::string reason; + bool opened = c.open(dir_path.wstring(), true, nullptr, &reason); + EXPECT_FALSE(opened); + EXPECT_TRUE(reason.find("could not be opened") != std::string::npos); + boost::filesystem::remove(dir_path); +} + +// corrupted file truncation uint32: +// simulate corrupted file tail on uint32_t and check truncation +TEST(pod_array_file_container, corrupted_file_truncation_uint32) +{ + auto path = make_temp_file(); + { + boost::filesystem::ofstream out(path, std::ios::binary | std::ios::out); + pod_t v = 0x12345678u; + out.write(reinterpret_cast(&v), sizeof(v)); + const char junk[3] = {9,8,7}; + out.write(junk, sizeof(junk)); + } + pod_container c; + bool corrupted = false; + std::string reason; + ASSERT_TRUE(c.open(path.wstring(), false, &corrupted, &reason)); + EXPECT_TRUE(corrupted); + EXPECT_EQ(c.size(), 1u); + pod_t read_v; + ASSERT_TRUE(c.get_item(0, read_v)); + EXPECT_EQ(read_v, 0x12345678u); + EXPECT_TRUE(reason.find("truncated") != std::string::npos); +} + +// operations without open: +// ensure push_back/get_item/size_bytes/size behave when container never opened +TEST(pod_array_file_container, operations_without_open) +{ + pod_container c; + EXPECT_FALSE(c.push_back(1u)); + pod_t dummy; + EXPECT_FALSE(c.get_item(0, dummy)); + EXPECT_EQ(c.size_bytes(), 0u); + EXPECT_EQ(c.size(), 0u); +} + +// checks stream state transitions +TEST(pod_array_file_container, is_opened_and_in_good_state) +{ + auto path = make_temp_file(); + pod_container c; + + // not opened yet + EXPECT_FALSE(c.is_opened_and_in_good_state()); + + // open for create + ASSERT_TRUE(c.open(path.wstring(), true)); + EXPECT_TRUE(c.is_opened_and_in_good_state()); + + // after close + c.close(); + EXPECT_FALSE(c.is_opened_and_in_good_state()); +} + +// wipes file contents and resets size +TEST(pod_array_file_container, clear_resets_file) +{ + auto path = make_temp_file(); + pod_container c; + ASSERT_TRUE(c.open(path.wstring(), true)); + + // add some elements + ASSERT_TRUE(c.push_back(123u)); + ASSERT_TRUE(c.push_back(456u)); + EXPECT_EQ(c.size(), 2u); + + // clear the container + ASSERT_TRUE(c.clear()); + EXPECT_EQ(c.size(), 0u); + + // file should still be usable + ASSERT_TRUE(c.push_back(789u)); + EXPECT_EQ(c.size(), 1u); +} \ No newline at end of file