diff --git a/.gitmodules b/.gitmodules index 97a855bd..57896bbb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -12,3 +12,6 @@ [submodule "contrib/jwt-cpp"] path = contrib/jwt-cpp url = https://github.com/Thalhammer/jwt-cpp.git +[submodule "contrib/bitcoin-secp256k1"] + path = contrib/bitcoin-secp256k1 + url = https://github.com/bitcoin-core/secp256k1.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 04790815..c480300f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,11 @@ set(USE_PCH FALSE CACHE BOOL "Use shared precompiled headers") set(DISABLE_TOR FALSE CACHE BOOL "Disable TOR library(and related tor-connect submodule)") set(TESTNET FALSE CACHE BOOL "Compile for testnet") set(BUILD_GUI FALSE CACHE BOOL "Build qt-daemon") +set(USE_BITCOIN_SECP256K1_FOR_ECDSA FALSE CACHE BOOL "Use bitcoin-secp256k1 library for validating ECDSA(instead of OpenSSL)") +if(NOT USE_BITCOIN_SECP256K1_FOR_ECDSA) + add_definitions(-DUSE_OPEN_SSL_FOR_ECDSA) +endif() + include_directories(src contrib/eos_portable_archive contrib contrib/epee/include contrib/jwt-cpp/include ${OPENSSL_INCLUDE_DIR} "${CMAKE_BINARY_DIR}/version" "${CMAKE_BINARY_DIR}/contrib/zlib") @@ -89,6 +94,7 @@ endif() if(CAKEWALLET) message("NOTICE: Building libraries for CAKEWALLET") add_definitions(-DCAKEWALLET) + add_definitions(-DDISABLE_PFR_SERIALIZATION_SELFCHECK) endif() set(OPENSSL_USE_STATIC_LIBS TRUE) # link statically @@ -226,7 +232,12 @@ endif() message("CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}") if(CMAKE_SYSTEM_NAME STREQUAL "iOS") set(CMAKE_OSX_DEPLOYMENT_TARGET 12.00) - set(Boost_LIBRARIES "libboost.a") + if(NOT DEFINED SKIP_BOOST_FATLIB_LIB OR NOT SKIP_BOOST_FATLIB_LIB) + message("Ios: libboost.a included as library") + set(Boost_LIBRARIES "libboost.a") + else() + message("Ios: libboost.a not included as library") + endif() #workaround for new XCode 12 policy for builds(now it includes a slice for the "arm64" when builds for simulator) set(__iphoneos_archs "arm64") #set(__iphonesimulator_archs "arm64,x86_64") @@ -235,8 +246,12 @@ if(CMAKE_SYSTEM_NAME STREQUAL "iOS") #set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] "${__iphonesimulator_archs}") #set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] "${__iphonesimulator_archs}") elseif(CMAKE_SYSTEM_NAME STREQUAL "Android") - 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") + if(CAKEWALLET) + find_package(Boost 1.71 REQUIRED COMPONENTS system filesystem thread timer date_time chrono regex serialization atomic program_options locale) + 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") + endif() set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fPIC") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fPIC") elseif(APPLE) @@ -254,7 +269,7 @@ include_directories(SYSTEM ${Boost_INCLUDE_DIRS} ${PROJECT_SOURCE_DIR}/contrib/e if(MINGW) set(Boost_LIBRARIES "${Boost_LIBRARIES};ws2_32;mswsock") elseif(NOT MSVC) - if(NOT APPLE) + if(NOT APPLE AND NOT CAKEWALLET) set(Boost_LIBRARIES "${Boost_LIBRARIES};") if(STATIC) message("NOTICE: Including static ICU libraries") diff --git a/README.md b/README.md index ea26c0cc..655e780a 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,10 @@ Be sure to clone the repository properly:\ |--|--|--|--| | gcc (Linux) | 5.4.0 | 9.4.0 | 12.3.0 | | llvm/clang (Linux) | UNKNOWN | 7.0.1 | 8.0.0 | -| [MSVC](https://visualstudio.microsoft.com/downloads/) (Windows) | 2017 (15.9.30) | 2019 (16.11.34) | 2022 (17.9.5) | +| [MSVC](https://visualstudio.microsoft.com/downloads/) (Windows) | 2017 (15.9.30) | 2019 (16.11.34) | 2022 (17.11.5) | | [XCode](https://developer.apple.com/downloads/) (macOS) | 12.3 | 14.3 | 15.2 | | [CMake](https://cmake.org/download/) | 3.15.5 | 3.26.3 | 3.29.0 | -| [Boost](https://www.boost.org/users/download/) | 1.70 | 1.70 | 1.84 | +| [Boost](https://www.boost.org/users/download/) | 1.75 | 1.84 | 1.84 | | [OpenSSL](https://www.openssl.org/source/) [(win)](https://slproweb.com/products/Win32OpenSSL.html) | 1.1.1n | 1.1.1w | 1.1.1w | | [Qt](https://download.qt.io/archive/qt/) (*only for GUI*) | 5.8.0 | 5.11.2 | 5.15.2 | @@ -52,10 +52,9 @@ Recommended OS versions: Ubuntu 20.04, 22.04 LTS. 3. Download and build Boost\ (Assuming you have cloned Zano into the 'zano' folder. If you used a different location for Zano, **edit line 4** accordingly.) - curl -OL https://boostorg.jfrog.io/artifactory/main/release/1.70.0/source/boost_1_70_0.tar.bz2 - echo "430ae8354789de4fd19ee52f3b1f739e1fba576f0aded0897c3c2bc00fb38778 boost_1_70_0.tar.bz2" | shasum -c && tar -xjf boost_1_70_0.tar.bz2 - rm boost_1_70_0.tar.bz2 && cd boost_1_70_0 - patch -p0 < ../zano/utils/boost_1.70_gcc_8.patch || cd .. + curl -OL https://boostorg.jfrog.io/artifactory/main/release/1.84.0/source/boost_1_84_0.tar.bz2 + echo "cc4b893acf645c9d4b698e9a0f08ca8846aa5d6c68275c14c3e7949c24109454 boost_1_84_0.tar.bz2" | shasum -c && tar -xjf boost_1_84_0.tar.bz2 + rm boost_1_84_0.tar.bz2 && cd boost_1_84_0 ./bootstrap.sh --with-libraries=system,filesystem,thread,date_time,chrono,regex,serialization,atomic,program_options,locale,timer,log ./b2 && cd .. Make sure that you see "The Boost C++ Libraries were successfully built!" message at the end. @@ -88,13 +87,13 @@ For instance, by adding the following lines to `~/.bashrc` [*server version*] - export BOOST_ROOT=/home/user/boost_1_70_0 + export BOOST_ROOT=/home/user/boost_1_84_0 export OPENSSL_ROOT_DIR=/home/user/openssl [*GUI version*] - export BOOST_ROOT=/home/user/boost_1_70_0 + export BOOST_ROOT=/home/user/boost_1_84_0 export OPENSSL_ROOT_DIR=/home/user/openssl export QT_PREFIX_PATH=/home/user/Qt5.11.2/5.11.2/gcc_64 diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index abfc4885..842f905c 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -5,6 +5,23 @@ add_subdirectory(zlib) add_subdirectory(db) add_subdirectory(ethereum) +if(USE_BITCOIN_SECP256K1_FOR_ECDSA) + option(SECP256K1_BUILD_BENCHMARK "Build benchmarks." OFF) + option(SECP256K1_BUILD_TESTS "Build tests." OFF) + option(SECP256K1_BUILD_EXHAUSTIVE_TESTS "Build exhaustive tests." OFF) + option(SECP256K1_BUILD_CTIME_TESTS "Build constant-time tests." OFF) + option(SECP256K1_BUILD_EXAMPLES "Build examples." OFF) + set_property(GLOBAL PROPERTY CTEST_TARGETS_ADDED 1) + + if(STATIC) + set(SECP256K1_DISABLE_SHARED ON CACHE BOOL "Disable shared library for secp256k1") + set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build static libraries by default" FORCE) + endif() + add_subdirectory(bitcoin-secp256k1) + set_property(TARGET secp256k1 PROPERTY FOLDER "contrib") + set_property(TARGET secp256k1_precomputed PROPERTY FOLDER "contrib") +endif() + if( NOT DISABLE_TOR) add_subdirectory(tor-connect) endif() @@ -23,6 +40,7 @@ set_property(TARGET libminiupnpc-static PROPERTY FOLDER "contrib") set_property(TARGET zlibstatic PROPERTY FOLDER "contrib") set_property(TARGET mdbx PROPERTY FOLDER "contrib") set_property(TARGET lmdb PROPERTY FOLDER "contrib") + if( NOT DISABLE_TOR) set_property(TARGET tor-connect PROPERTY FOLDER "contrib") endif() diff --git a/contrib/bitcoin-secp256k1 b/contrib/bitcoin-secp256k1 new file mode 160000 index 00000000..a5269373 --- /dev/null +++ b/contrib/bitcoin-secp256k1 @@ -0,0 +1 @@ +Subproject commit a5269373fa13ff845f654d81b90629dd78495641 diff --git a/contrib/epee/include/file_io_utils.h b/contrib/epee/include/file_io_utils.h index 9d5118cb..bb21ae99 100644 --- a/contrib/epee/include/file_io_utils.h +++ b/contrib/epee/include/file_io_utils.h @@ -35,7 +35,19 @@ #include #include #include -#include + + +#if __has_include() + #include + namespace stdfs = std::filesystem; +#else + #if TARGET_OS_IOS + #error "This should never happen on ios." + #endif + namespace stdfs = boost::filesystem; +#endif + +//#include #ifndef MAKE64 #define MAKE64(low,high) ((__int64)(((DWORD)(low)) | ((__int64)((DWORD)(high))) << 32)) @@ -562,10 +574,10 @@ namespace file_io_utils try { - std::filesystem::directory_iterator end_itr; // default construction yields past-the-end - for ( std::filesystem::directory_iterator itr( epee::string_encoding::utf8_to_wstring(path) ); itr != end_itr; ++itr ) + stdfs::directory_iterator end_itr; // default construction yields past-the-end + for (stdfs::directory_iterator itr( epee::string_encoding::utf8_to_wstring(path) ); itr != end_itr; ++itr ) { - if ( only_files && std::filesystem::is_directory(itr->status()) ) + if ( only_files && stdfs::is_directory(itr->status()) ) { continue; } diff --git a/contrib/epee/include/misc_language.h b/contrib/epee/include/misc_language.h index 3ee7001e..c200a3d0 100644 --- a/contrib/epee/include/misc_language.h +++ b/contrib/epee/include/misc_language.h @@ -84,9 +84,9 @@ namespace misc_utils { template - void cast_assign_a_to_b(t_type_a& a, const t_type_b& b) + void cast_assign_a_to_b(const t_type_a& a, t_type_b& b) { - *static_cast(&a) = b; + *static_cast(&b) = a; } template // Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net // All rights reserved. @@ -90,7 +90,7 @@ DISABLE_VS_WARNINGS(4100) #define LOG_JOURNAL_MAX_ELEMENTS 100 #ifdef _DEBUG - #define _ASSERTE__(expr) if(!expr) {__debugbreak();} + #define _ASSERTE__(expr) if(!(expr)) {__debugbreak();} #else #define _ASSERTE__(expr) #endif diff --git a/contrib/epee/include/net/http_server_handlers_map2.h b/contrib/epee/include/net/http_server_handlers_map2.h index b4466ad4..9e1ad044 100644 --- a/contrib/epee/include/net/http_server_handlers_map2.h +++ b/contrib/epee/include/net/http_server_handlers_map2.h @@ -282,8 +282,15 @@ namespace epee { namespace http { struct i_chain_handler { + virtual bool handle_http_request(const epee::net_utils::http::http_request_info& query_info, epee::net_utils::http::http_response_info& response_info, + epee::net_utils::connection_context_base& conn_context, bool& call_found, documentation& docs = epee::net_utils::http::i_chain_handler::m_empty_documentation) + { + return this->handle_http_request_map(query_info, response_info, conn_context, call_found, docs); + } + virtual bool handle_http_request_map(const epee::net_utils::http::http_request_info& query_info, epee::net_utils::http::http_response_info& response_info, epee::net_utils::connection_context_base& m_conn_context, bool& call_found, documentation& docs = epee::net_utils::http::i_chain_handler::m_empty_documentation) = 0; + static inline documentation m_empty_documentation; }; @@ -365,7 +372,7 @@ namespace epee { LOG_PRINT( "[HTTP/BIN][" << epee::string_tools::get_ip_string_from_int32(m_conn_context.m_remote_ip ) << "][" << query_info.m_URI << "] processed with " << ticks1-ticks << "/"<< ticks2-ticks1 << "/" << ticks3-ticks2 << "ms", LOG_LEVEL_2); \ } -#define CHAIN_TO_PHANDLER(pi_chain_handler) else if (pi_chain_handler && pi_chain_handler->handle_http_request_map(query_info, response_info, m_conn_context, call_found, docs) && call_found) { return true;} +#define CHAIN_TO_PHANDLER(pi_chain_handler) else if (pi_chain_handler && pi_chain_handler->handle_http_request(query_info, response_info, m_conn_context, call_found, docs) && call_found) { return true;} #define CHAIN_URI_MAP2(callback) else {callback(query_info, response_info, m_conn_context);call_found = true;} diff --git a/contrib/epee/include/serialization/keyvalue_helpers.h b/contrib/epee/include/serialization/keyvalue_helpers.h index d76fc641..ae72d4b3 100644 --- a/contrib/epee/include/serialization/keyvalue_helpers.h +++ b/contrib/epee/include/serialization/keyvalue_helpers.h @@ -25,6 +25,8 @@ // #pragma once +#include +#include #include "misc_language.h" namespace epee { @@ -51,24 +53,73 @@ namespace epee } }; + template + struct is_std_optional : std::false_type {}; + + template + struct is_std_optional> : std::true_type {}; + + + template + struct is_std_optional> : std::true_type {}; + + + //basic helpers for pod-to-hex serialization + template + std::string transform_t_pod_to_str_internal(const t_pod_type& a) + { + return epee::string_tools::pod_to_hex(a); + } + + template + std::string transform_t_pod_to_str_internal(const std::optional& a) + { + if (a.has_value()) + return epee::string_tools::pod_to_hex(*a); + else + return ""; + } + + template + std::string transform_t_pod_to_str_internal(const boost::optional& a) + { + if (a.has_value()) + return epee::string_tools::pod_to_hex(*a); + else + return ""; + } //basic helpers for pod-to-hex serialization template std::string transform_t_pod_to_str(const t_pod_type & a) { - return epee::string_tools::pod_to_hex(a); + return transform_t_pod_to_str_internal(a); } - template + + + + template t_pod_type transform_str_to_t_pod(const std::string& a) { - t_pod_type res = AUTO_VAL_INIT(res); + t_pod_type res = AUTO_VAL_INIT(res); if (a.empty()) return res; + if constexpr (is_std_optional::value) + { + typename t_pod_type::value_type v = AUTO_VAL_INIT(v); + if (!epee::string_tools::hex_to_pod(a, v)) + throw std::runtime_error(std::string("Unable to transform \"") + a + "\" to pod type " + typeid(typename t_pod_type::value_type).name()); + return v; + } + + if (!epee::string_tools::hex_to_pod(a, res)) throw std::runtime_error(std::string("Unable to transform \"") + a + "\" to pod type " + typeid(t_pod_type).name()); return res; } + + //basic helpers for blob-to-hex serialization inline std::string transform_binbuf_to_hexstr(const std::string& a) diff --git a/contrib/epee/include/serialization/keyvalue_serialization_overloads.h b/contrib/epee/include/serialization/keyvalue_serialization_overloads.h index cb88897f..1bd2db1c 100644 --- a/contrib/epee/include/serialization/keyvalue_serialization_overloads.h +++ b/contrib/epee/include/serialization/keyvalue_serialization_overloads.h @@ -1,3 +1,4 @@ +// Copyright (c) 2024, Zano Project // Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net // All rights reserved. // @@ -359,8 +360,12 @@ namespace epee template< class t_type_stored, class t_type, class t_storage, typename cb_serialize> static bool serialize_ephemeral(const t_type& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname, cb_serialize cb_s) { - t_type_stored a = cb_s(d); - return epee::serialization::selector::serialize(a, stg, hparent_section, pname); + t_type_stored a = AUTO_VAL_INIT(a); + bool add_val = cb_s(d, a); + if (add_val) + return epee::serialization::selector::serialize(a, stg, hparent_section, pname); + else + return true; } }; @@ -473,6 +478,29 @@ namespace epee return r; } //------------------------------------------------------------------------------------------------------------------- + //std::optional + template + bool kv_serialize(const std::optional& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + if(d.has_value()) + { + return kv_serialize(*d, stg, hparent_section, pname); + } + return true; + } + //------------------------------------------------------------------------------------------------------------------- + template + bool kv_unserialize(std::optional& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + d = t_type{}; + bool r = kv_unserialize(*d, stg, hparent_section, pname); + if (!r) + { + d = std::nullopt; + } + return r; + } + //------------------------------------------------------------------------------------------------------------------- //boost::shared_ptr template bool kv_serialize(const boost::shared_ptr& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) @@ -496,6 +524,30 @@ namespace epee } return r; } + //------------------------------------------------------------------------------------------------------------------- + //std::shared_ptr + template + bool kv_serialize(const std::shared_ptr& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + if (d.get()) + { + return kv_serialize(*d, stg, hparent_section, pname); + } + return true; + } + //------------------------------------------------------------------------------------------------------------------- + template + bool kv_unserialize(std::shared_ptr& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + d.reset(); + t_type* ptr = new t_type(); + bool r = kv_unserialize(*ptr, stg, hparent_section, pname); + if (!r) + { + d.reset(ptr); + } + return r; + } } } \ No newline at end of file diff --git a/contrib/epee/include/string_tools.h b/contrib/epee/include/string_tools.h index 24dbffee..8cf56e5a 100644 --- a/contrib/epee/include/string_tools.h +++ b/contrib/epee/include/string_tools.h @@ -444,7 +444,7 @@ POP_GCC_WARNINGS inline bool string_to_num_fast(const std::string& buff, int& val) { val = atoi(buff.c_str()); - if(buff != "0" && val == 0) + if (val == 0 && buff.find_first_not_of('0') != std::string::npos) return false; return true; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 200096f3..4505b8f8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -116,6 +116,13 @@ else() endif() add_library(crypto ${CRYPTO}) +if(USE_BITCOIN_SECP256K1_FOR_ECDSA) + add_dependencies(crypto secp256k1) + target_link_libraries(crypto secp256k1) +else() + add_dependencies(crypto OpenSSL::Crypto) + target_link_libraries(crypto OpenSSL::Crypto) +endif() add_library(currency_core ${CURRENCY_CORE}) add_dependencies(currency_core version ${PCH_LIB_NAME}) diff --git a/src/common/boost_serialization_maps.h b/src/common/boost_serialization_maps.h index 7f44e4d2..f6704b95 100644 --- a/src/common/boost_serialization_maps.h +++ b/src/common/boost_serialization_maps.h @@ -3,8 +3,11 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #pragma once - -#define BEGIN_BOOST_SERIALIZATION() template inline void serialize(t_archive &_arch, const unsigned int ver) { +#include +#ifndef DISABLE_PFR_SERIALIZATION_SELFCHECK + #include +#endif +#define BEGIN_BOOST_SERIALIZATION() template void serialize(t_archive &_arch, const unsigned int ver) { template struct TAssertEquality { static_assert(A == B, "Serialization map is not updated, sizeof() missmatch"); @@ -26,6 +29,60 @@ template struct TAssertEquality { #define END_BOOST_SERIALIZATION() } + +/********************************************************************************************************************************** + This serialization closing macro adds self-validation by checking the total number of fields in the structure using boost::pfr. + + Note: "num_fields" does NOT represent the number of fields included in the serialization. Instead, it indicates the total number + of fields in the structure, some of which might not be included in the serialization for valid reasons. If someone adds new + fields to the structure but forgets to update the serialization map, the compilation will fail. Any update to "num_fields" must + be accompanied by a thorough review of the serialization map to ensure no fields are omitted. +**********************************************************************************************************************************/ +#ifndef DISABLE_PFR_SERIALIZATION_SELFCHECK + #define END_BOOST_SERIALIZATION_TOTAL_FIELDS(num_fields) static_assert(num_fields == boost::pfr::tuple_size::type>::value, "Unexpected number of fields!"); } +#else + #define END_BOOST_SERIALIZATION_TOTAL_FIELDS(num_fields) END_BOOST_SERIALIZATION() +#endif + +#define BOOST_SERIALIZATION_CURRENT_ARCHIVE_VER(current_version) static const unsigned int current_boost_version_serialization_version = current_version; + +#define LOOP_BACK_BOOST_SERIALIZATION_VERSION(type) BOOST_CLASS_VERSION(type, type::current_boost_version_serialization_version); + + + +template +struct boost_transition_t {}; + +template +struct boost_transition_t +{ + template + static void chain_serialize(archive& ar, const origin_type& origin_tx) + { + destination_t dst_tx = AUTO_VAL_INIT(dst_tx); + transition_convert(origin_tx, dst_tx); + ar & dst_tx; + } +}; + +template +struct boost_transition_t +{ + template + static void chain_serialize(archive& ar, origin_type& origin_tx) + { + // TODO: consider using move semantic for temporary 'dst_tx' + destination_t dst_tx = AUTO_VAL_INIT(dst_tx); + ar & dst_tx; + transition_convert(dst_tx, origin_tx); + } +}; + + +#define BOOST_CHAIN_TRANSITION_VER(obj_version, old_type) if (obj_version == ver) {boost_transition_t::chain_serialize(_arch, *this);return;} + +#define BOOST_CHAIN_TRANSITION_IF_COND_TRUE(condition, old_type) if (condition) {boost_transition_t::chain_serialize(_arch, *this);return;} + /* example of use: diff --git a/src/common/crypto_serialization.h b/src/common/crypto_serialization.h index dd988a68..21845ae9 100644 --- a/src/common/crypto_serialization.h +++ b/src/common/crypto_serialization.h @@ -21,6 +21,7 @@ #include "crypto/clsag.h" #include "crypto/zarcanum.h" #include "crypto/one_out_of_many_proofs.h" +#include "crypto/eth_signature.h" #include "boost_serialization_maps.h" #include "serialization/keyvalue_enable_POD_serialize_as_string.h" // @@ -230,6 +231,8 @@ BLOB_SERIALIZER(crypto::key_image); BLOB_SERIALIZER(crypto::signature); BLOB_SERIALIZER(crypto::scalar_t); BLOB_SERIALIZER(crypto::point_t); +BLOB_SERIALIZER(crypto::eth_public_key); +BLOB_SERIALIZER(crypto::eth_signature); VARIANT_TAG(debug_archive, crypto::hash, "hash"); VARIANT_TAG(debug_archive, crypto::public_key, "public_key"); @@ -237,6 +240,8 @@ VARIANT_TAG(debug_archive, crypto::secret_key, "secret_key"); VARIANT_TAG(debug_archive, crypto::key_derivation, "key_derivation"); VARIANT_TAG(debug_archive, crypto::key_image, "key_image"); VARIANT_TAG(debug_archive, crypto::signature, "signature"); +VARIANT_TAG(debug_archive, crypto::eth_public_key, "eth_public_key"); +VARIANT_TAG(debug_archive, crypto::eth_signature, "eth_signature"); // @@ -245,6 +250,8 @@ VARIANT_TAG(debug_archive, crypto::signature, "signature"); KV_ENABLE_POD_SERIALIZATION_AS_HEX(crypto::scalar_t); KV_ENABLE_POD_SERIALIZATION_AS_HEX(crypto::hash); +KV_ENABLE_POD_SERIALIZATION_AS_HEX(crypto::eth_public_key); +KV_ENABLE_POD_SERIALIZATION_AS_HEX(crypto::eth_signature); // // Boost serialization @@ -296,5 +303,28 @@ namespace boost { a & reinterpret_cast(x); } + template + inline void serialize(Archive &a, crypto::eth_public_key &x, const boost::serialization::version_type ver) + { + a & reinterpret_cast(x); + } + template + inline void serialize(Archive &a, crypto::eth_signature &x, const boost::serialization::version_type ver) + { + a & reinterpret_cast(x); + } + template + inline void serialize(Archive& a, crypto::scalar_vec_t& x, const boost::serialization::version_type ver) + { + static_assert(sizeof(std::vector) == sizeof(crypto::scalar_vec_t)); + a & static_cast&>(x); + } + template + inline void serialize(Archive& a, crypto::scalar_mat_t& x, const boost::serialization::version_type ver) + { + static_assert(sizeof(std::vector) == sizeof(crypto::scalar_mat_t)); + a & static_cast&>(x); + } + } // namespace serialization } // namespace boost diff --git a/src/common/db_backend_lmdb.cpp b/src/common/db_backend_lmdb.cpp index 75e60ce4..d23fff80 100644 --- a/src/common/db_backend_lmdb.cpp +++ b/src/common/db_backend_lmdb.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2019 Zano Project +// Copyright (c) 2014-2024 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. @@ -394,10 +394,67 @@ namespace tools } return true; } + const char* lmdb_db_backend::name() { return "lmdb"; } + + bool lmdb_db_backend::convert_db_4kb_page_to_16kb_page(const std::string& source_path, const std::string& destination_path) + { + #define MDB_CHECK(x, msg) {int rc = x; CHECK_AND_ASSERT_MES(rc == MDB_SUCCESS, false, "LMDB 4k->16k error: " << msg << ": " << mdb_strerror(rc));} + + MDB_env *env_src = nullptr, *env_dst = nullptr; + + // source + MDB_CHECK(mdb_env_create(&env_src), "failed to create LMDB environment"); + MDB_CHECK(mdb_env_set_mapsize(env_src, 4 * 1024 * 1024), "failed to set mapsize"); // mapsize ? + MDB_CHECK(mdb_env_open(env_src, source_path.c_str(), 0, 0664), "failed to open source LMDB"); + + // destination (16k page size) + MDB_CHECK(mdb_env_create(&env_dst), "failed to create LMDB environment"); + MDB_CHECK(mdb_env_set_mapsize(env_dst, 16 * 1024 * 1024), "failed to set mapsize"); // mapsize ? + + // TODO uncomment after mdb_env_set_pagesize is supported + // MDB_CHECK(mdb_env_set_pagesize(env_dst, 16 * 1024), "failed to set page size to 16K"); + + MDB_CHECK(mdb_env_open(env_dst, destination_path.c_str(), 0, 0664), "failed to open destination LMDB"); + + // begin transactions + MDB_txn *txn_src = nullptr, *txn_dst = nullptr; + MDB_dbi dbi_src, dbi_dst; + MDB_CHECK(mdb_txn_begin(env_src, nullptr, MDB_RDONLY, &txn_src), "failed to begin source transaction"); + MDB_CHECK(mdb_dbi_open(txn_src, nullptr, 0, &dbi_src), "failed to open source database"); + MDB_CHECK(mdb_txn_begin(env_dst, nullptr, 0, &txn_dst), "failed to begin destination transaction"); + MDB_CHECK(mdb_dbi_open(txn_dst, nullptr, MDB_CREATE, &dbi_dst), "failed to open destination database"); + + MDB_cursor *cursor; + MDB_val key, data; + + // Iterate over the source database and copy all key-value pairs to the destination database + MDB_CHECK(mdb_cursor_open(txn_src, dbi_src, &cursor), "failed to open cursor"); + + while (mdb_cursor_get(cursor, &key, &data, MDB_NEXT) == MDB_SUCCESS) + { + MDB_CHECK(mdb_put(txn_dst, dbi_dst, &key, &data, 0), "failed to put data in destination database"); + } + + mdb_cursor_close(cursor); + + // commit transactions + MDB_CHECK(mdb_txn_commit(txn_src), "failed to commit source transaction"); + MDB_CHECK(mdb_txn_commit(txn_dst), "failed to commit destination transaction"); + + mdb_dbi_close(env_src, dbi_src); + mdb_dbi_close(env_dst, dbi_dst); + mdb_env_close(env_src); + mdb_env_close(env_dst); + + return true; + + #undef MDB_CHECK + } + } } diff --git a/src/common/db_backend_lmdb.h b/src/common/db_backend_lmdb.h index ef56c346..c2920f0c 100644 --- a/src/common/db_backend_lmdb.h +++ b/src/common/db_backend_lmdb.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2019 Zano Project +// Copyright (c) 2014-2024 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. @@ -36,6 +36,7 @@ namespace tools boost::recursive_mutex m_write_exclusive_lock; std::map m_txs; // size_t -> count of nested read_only transactions bool pop_tx_entry(tx_entry& txe); + public: lmdb_db_backend(); ~lmdb_db_backend(); @@ -60,6 +61,8 @@ namespace tools bool have_tx(); MDB_txn* get_current_tx(); + static bool convert_db_4kb_page_to_16kb_page(const std::string& source_path, const std::string& destination_path); + }; } } diff --git a/src/common/mnemonic-encoding.cpp b/src/common/mnemonic-encoding.cpp index c2e3e307..f52ece6b 100644 --- a/src/common/mnemonic-encoding.cpp +++ b/src/common/mnemonic-encoding.cpp @@ -3391,5 +3391,9 @@ namespace tools CHECK_AND_ASSERT_THROW_MES(it!= wordsMap.end(), "unable to find word \"" << w << "\" in mnemonic dictionary"); return it->second; } + const map& get_words_map() + { + return wordsMap; + } } } diff --git a/src/common/mnemonic-encoding.h b/src/common/mnemonic-encoding.h index 4ce6b9bb..5e235399 100644 --- a/src/common/mnemonic-encoding.h +++ b/src/common/mnemonic-encoding.h @@ -47,5 +47,6 @@ namespace tools std::string word_by_num(uint32_t n); uint64_t num_by_word(const std::string& w); bool valid_word(const std::string& w); + const std::map& get_words_map(); } } diff --git a/src/common/pre_download.h b/src/common/pre_download.h index ec12df55..3c46373f 100644 --- a/src/common/pre_download.h +++ b/src/common/pre_download.h @@ -21,8 +21,8 @@ namespace tools }; #ifndef TESTNET - static constexpr pre_download_entry c_pre_download_mdbx = { "http://95.217.42.247/pre-download/zano_mdbx_95_2500000.pak", "8ffa2cb4213f4f96f97033c65a9e52bc350f683237808597784e79b24d5bfee7", 3242348793, 5905489920 }; - static constexpr pre_download_entry c_pre_download_lmdb = { "http://95.217.42.247/pre-download/zano_lmdb_95_2500000.pak", "5509650e12c8f901e6731a2bfaf3abfd64409e3e1366d3d94cd11db8beddb0c3", 4239505801, 5893566464 }; + static constexpr pre_download_entry c_pre_download_mdbx = { "http://95.217.42.247/pre-download/zano_mdbx_95_2892700.pak", "68e819cd119e4af1b81f1852e42978d662f1e6355124352f3e835db32b5a8230", 6414724487, 10468823040 }; + static constexpr pre_download_entry c_pre_download_lmdb = { "http://95.217.42.247/pre-download/zano_lmdb_95_2892700.pak", "605eb4eb0903aa7b3a2a046514ef349d45c7de31d2702fd9dc104ca65705d6eb", 7860127140, 10204872704 }; #else static constexpr pre_download_entry c_pre_download_mdbx = { "", "", 0, 0 }; static constexpr pre_download_entry c_pre_download_lmdb = { "", "", 0, 0 }; diff --git a/src/common/util.cpp b/src/common/util.cpp index 1fdbe537..f87f0582 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -658,31 +658,68 @@ std::string get_nix_version_display_string() return static_cast(in.tellg()); } - bool check_remote_client_version(const std::string& client_ver) + bool parse_client_version(const std::string& str, int& major, int& minor, int& revision, int& build_number, std::string& commit_id, bool& dirty) { - std::string v = client_ver.substr(0, client_ver.find('[')); // remove commit id - v = v.substr(0, v.rfind('.')); // remove build number + // "10.101.999.28391" + // "10.101.999.28391[deadbeef31337]" + // "10.101.999.28391[deadbeef31337-dirty]" + // 0123456789012345678901234567890123456 - int v_major = 0, v_minor = 0, v_revision = 0; - - size_t dot_pos = v.find('.'); - if (dot_pos == std::string::npos || !epee::string_tools::string_to_num_fast(v.substr(0, dot_pos), v_major)) + if (str.size() == 0) return false; - v = v.substr(dot_pos + 1); - dot_pos = v.find('.'); - if (!epee::string_tools::string_to_num_fast(v.substr(0, dot_pos), v_minor)) - return false; - - if (dot_pos != std::string::npos) + auto bracket_pos = str.find('['); + if (bracket_pos != std::string::npos) { - // revision - v = v.substr(dot_pos + 1); - if (!epee::string_tools::string_to_num_fast(v, v_revision)) + if (str[str.size() - 1] != ']') return false; + + commit_id = str.substr(bracket_pos + 1, str.size() - bracket_pos - 2); + auto d_pos = commit_id.find("-dirty"); + if (d_pos != std::string::npos) + { + dirty = true; + commit_id.erase(d_pos); + } } - // got v_major, v_minor, v_revision + std::string ver_str = str.substr(0, bracket_pos); + std::vector versions; + boost::split(versions, ver_str, boost::is_any_of(".")); + if (versions.size() != 4) + return false; + + if (!epee::string_tools::string_to_num_fast(versions[0], major)) + return false; + + if (!epee::string_tools::string_to_num_fast(versions[1], minor)) + return false; + + if (!epee::string_tools::string_to_num_fast(versions[2], revision)) + return false; + + if (!epee::string_tools::string_to_num_fast(versions[3], build_number)) + return false; + + return true; + } + + bool parse_client_version_build_number(const std::string& str, int& build_number) + { + int major = -1, minor = -1, revision = -1; + std::string commit_id; + bool dirty = false; + return tools::parse_client_version(str, major, minor, revision, build_number, commit_id, dirty); + } + + bool check_remote_client_version(const std::string& client_ver) + { + int v_major = 0, v_minor = 0, v_revision = 0, v_build_number = 0; + std::string commit_id; + bool dirty_flag = false; + + if (!parse_client_version(client_ver, v_major, v_minor, v_revision, v_build_number, commit_id, dirty_flag)) + return false; // allow 2.x and greater if (v_major < 2) diff --git a/src/common/util.h b/src/common/util.h index 0c5b0bb1..7761775d 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -37,6 +37,9 @@ namespace tools std::string get_current_username(); std::string get_os_version_string(); bool copy_dir(boost::filesystem::path const & source, boost::filesystem::path const & destination); + + bool parse_client_version(const std::string& str, int& major, int& minor, int& revision, int& build_number, std::string& commit_id, bool& dirty); + bool parse_client_version_build_number(const std::string& str, int& build_number); bool check_remote_client_version(const std::string& client_ver); bool create_directories_if_necessary(const std::string& path); diff --git a/src/connectivity_tool/conn_tool.cpp b/src/connectivity_tool/conn_tool.cpp index 07528c56..a7c32892 100644 --- a/src/connectivity_tool/conn_tool.cpp +++ b/src/connectivity_tool/conn_tool.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2019 Zano Project +// Copyright (c) 2014-2024 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 @@ -25,6 +25,7 @@ using namespace epee; #include "storages/http_abstract_invoke.h" #include "net/http_client.h" #include "currency_core/genesis_acc.h" +#include "common/db_backend_lmdb.h" #include namespace po = boost::program_options; @@ -63,6 +64,7 @@ namespace const command_line::arg_descriptor arg_pack_file ("pack-file", "perform gzip-packing and calculate hash for a given file"); const command_line::arg_descriptor arg_unpack_file ("unpack-file", "Perform gzip-unpacking and calculate hash for a given file"); const command_line::arg_descriptor arg_target_file ("target-file", "Specify target file for pack-file and unpack-file commands"); + const command_line::arg_descriptor arg_lmdb_page_4to16 ("convert-lmdb-4to16", "Perform LMDB conversion from 4k page size to 16k page size"); //const command_line::arg_descriptor arg_send_ipc ("send-ipc", "Send IPC request to UI"); } @@ -1220,7 +1222,10 @@ bool handle_pack_file(po::variables_map& vm) } if (!command_line::has_arg(vm, arg_target_file)) + { std::cout << "Error: Parameter target_file is not set." << ENDL; + return false; + } path_target = command_line::get_arg(vm, arg_target_file); std::ifstream source; @@ -1251,6 +1256,34 @@ bool handle_pack_file(po::variables_map& vm) } } +bool handle_lmdb_page_4to16(po::variables_map& vm) +{ + std::string path_source; + std::string path_target; + + if (!command_line::has_arg(vm, arg_lmdb_page_4to16)) + return false; + + path_source = command_line::get_arg(vm, arg_lmdb_page_4to16); + + if (!command_line::has_arg(vm, arg_target_file)) + { + std::cout << "Error: Parameter target_file is not set." << ENDL; + return false; + } + path_target = command_line::get_arg(vm, arg_target_file); + + if (tools::db::lmdb_db_backend::convert_db_4kb_page_to_16kb_page(path_source, path_target)) + { + std::cout << "Conversion failed" << ENDL; + return false; + } + + std::cout << "Converted successfully" << ENDL; + return true; +} + + //--------------------------------------------------------------------------------------------------------------- int main(int argc, char* argv[]) @@ -1375,6 +1408,10 @@ int main(int argc, char* argv[]) { return handle_pack_file(vm) ? EXIT_SUCCESS : EXIT_FAILURE; } + else if (command_line::has_arg(vm, arg_lmdb_page_4to16)) + { + return handle_lmdb_page_4to16(vm) ? EXIT_SUCCESS : EXIT_FAILURE; + } /*else if (command_line::has_arg(vm, arg_send_ipc)) { handle_send_ipc(command_line::get_arg(vm, arg_send_ipc)) ? EXIT_SUCCESS : EXIT_FAILURE; diff --git a/src/crypto/crypto-sugar.h b/src/crypto/crypto-sugar.h index 846e4572..3a76fa59 100644 --- a/src/crypto/crypto-sugar.h +++ b/src/crypto/crypto-sugar.h @@ -8,6 +8,7 @@ #include #include #include "crypto.h" +#include "eth_signature.h" namespace crypto { @@ -50,38 +51,42 @@ namespace crypto } - template - std::string pod_to_hex_reversed(const pod_t &h) + inline std::string buff_to_hex(const void* pdata, size_t len, bool reversed = false) { constexpr char hexmap[] = "0123456789abcdef"; - const unsigned char* data = reinterpret_cast(&h); - size_t len = sizeof h; + const unsigned char* data = reinterpret_cast(pdata); std::string s(len * 2, ' '); - for (size_t i = 0; i < len; ++i) + if (!reversed) { - s[2 * i] = hexmap[data[len - 1 - i] >> 4]; - s[2 * i + 1] = hexmap[data[len - 1 - i] & 0x0F]; + for (size_t i = 0; i < len; ++i) + { + s[2 * i] = hexmap[data[i] >> 4]; + s[2 * i + 1] = hexmap[data[i] & 0x0F]; + } + } + else + { + for (size_t i = 0; i < len; ++i) + { + s[2 * i] = hexmap[data[len - 1 - i] >> 4]; + s[2 * i + 1] = hexmap[data[len - 1 - i] & 0x0F]; + } } return s; } + template + std::string pod_to_hex_reversed(const pod_t &h) + { + return buff_to_hex(&h, sizeof h, true); + } + template std::string pod_to_hex(const pod_t &h) { - constexpr char hexmap[] = "0123456789abcdef"; - const unsigned char* data = reinterpret_cast(&h); - size_t len = sizeof h; - - std::string s(len * 2, ' '); - for (size_t i = 0; i < len; ++i) - { - s[2 * i] = hexmap[data[i] >> 4]; - s[2 * i + 1] = hexmap[data[i] & 0x0F]; - } - - return s; + return buff_to_hex(&h, sizeof h); } template @@ -811,26 +816,7 @@ namespace crypto friend bool operator==(const point_t& lhs, const point_t& rhs) { - // TODO: @#@# (performance) consider checking (lhs - rhs).is_zero() instead - - // convert to xy form, then compare components (because (x, y, z, t) representation is not unique) - fe lrecip, lx, ly; - fe rrecip, rx, ry; - - fe_invert(lrecip, lhs.m_p3.Z); - fe_invert(rrecip, rhs.m_p3.Z); - - fe_mul(lx, lhs.m_p3.X, lrecip); - fe_mul(rx, rhs.m_p3.X, rrecip); - if (memcmp(&lx, &rx, sizeof lx) != 0) - return false; - - fe_mul(ly, lhs.m_p3.Y, lrecip); - fe_mul(ry, rhs.m_p3.Y, rrecip); - if (memcmp(&ly, &ry, sizeof ly) != 0) - return false; - - return true; + return (lhs - rhs).is_zero(); } friend bool operator!=(const point_t& lhs, const point_t& rhs) @@ -1097,7 +1083,6 @@ namespace crypto make_random(); } - }; // scalar_vec_t @@ -1209,6 +1194,16 @@ namespace crypto m_elements.emplace_back(pk); } + void add_eth_pub_key(const crypto::eth_public_key& epk) + { + static_assert(sizeof(item_t) == 32, "unexpected size of hs_t::item_t"); + static_assert(sizeof epk.data == 33, "unexpected size of eth_public_key"); + m_elements.emplace_back(c_scalar_0); + m_elements.emplace_back(c_scalar_0); + char* p = m_elements[m_elements.size() - 2].c; // pointer to the first of the two added items + memcpy(p, &epk.data, sizeof epk.data); + } + void add_key_image(const crypto::key_image& ki) { m_elements.emplace_back(ki); diff --git a/src/crypto/eth_signature.cpp b/src/crypto/eth_signature.cpp new file mode 100644 index 00000000..c5e0999b --- /dev/null +++ b/src/crypto/eth_signature.cpp @@ -0,0 +1,404 @@ +// Copyright (c) 2024 Zano Project +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "eth_signature.h" +#include "crypto.h" +#ifndef USE_OPEN_SSL_FOR_ECDSA + #include "bitcoin-secp256k1/include/secp256k1.h" +#endif +#include "random.h" +#include "misc_language.h" +#include + + +#ifdef USE_OPEN_SSL_FOR_ECDSA + #include + #include + #include + #include + #include +#endif + + +// Function to create EC_KEY from raw 32 - byte private key +EC_KEY * create_ec_key_from_private_key(const unsigned char* private_key) { + EC_KEY* key = EC_KEY_new_by_curve_name(NID_secp256k1); + if (!key) { + std::cerr << "Failed to create new EC Key" << std::endl; + return nullptr; + } + + BIGNUM* priv_key_bn = BN_bin2bn(private_key, 32, nullptr); + if (!priv_key_bn) { + std::cerr << "Failed to convert private key to BIGNUM" << std::endl; + EC_KEY_free(key); + return nullptr; + } + + if (!EC_KEY_set_private_key(key, priv_key_bn)) { + std::cerr << "Failed to set private key" << std::endl; + EC_KEY_free(key); + BN_free(priv_key_bn); + return nullptr; + } + + BN_free(priv_key_bn); + return key; +} + + +void ensure_canonical_s(BIGNUM* s, const EC_GROUP* group) { + // Get the order of the curve + BIGNUM* order = BN_new(); + EC_GROUP_get_order(group, order, nullptr); + + // Compute half of the order: `n / 2` + BIGNUM* half_order = BN_new(); + BN_rshift1(half_order, order); + + // If `s` is greater than `n / 2`, replace `s` with `n - s` + if (BN_cmp(s, half_order) > 0) { + BN_sub(s, order, s); + } + + BN_free(order); + BN_free(half_order); +} + +// Update the function to ensure canonical `s` +bool generate_ethereum_signature(const unsigned char* hash, const unsigned char* private_key, crypto::eth_signature& sig_res) { + EC_KEY* ec_key = create_ec_key_from_private_key(private_key); + if (!ec_key) { + throw std::runtime_error("Failed to create EC key from private key"); + } + + // Sign the hash + unsigned int sig_len = ECDSA_size(ec_key); + std::vector signature(sig_len); + if (ECDSA_sign(0, hash, 32, signature.data(), &sig_len, ec_key) == 0) { + EC_KEY_free(ec_key); + throw std::runtime_error("Failed to create signature"); + } + signature.resize(sig_len); + + + // The OpenSSL ECDSA signature output is DER encoded, Ethereum expects (r, s, v) + const unsigned char* p = signature.data(); + ECDSA_SIG* sig = d2i_ECDSA_SIG(nullptr, &p, sig_len); + if (!sig) { + EC_KEY_free(ec_key); + throw std::runtime_error("Failed to parse ECDSA signature"); + } + + const BIGNUM* r = nullptr; + const BIGNUM* s = nullptr; + ECDSA_SIG_get0(sig, &r, &s); + + // Ensure canonical `s` + BIGNUM* s_canonical = BN_dup(s); + ensure_canonical_s(s_canonical, EC_KEY_get0_group(ec_key)); + + BN_bn2binpad(r, (unsigned char* )&sig_res.data[0], 32); + BN_bn2binpad(s_canonical, (unsigned char*)&sig_res.data[32], 32); + + + ECDSA_SIG_free(sig); + BN_free(s_canonical); + EC_KEY_free(ec_key); + return true; +} + +// Convert raw 33-byte compressed public key to EC_KEY object +EC_KEY* create_ec_key_from_compressed_public_key(const unsigned char* compressed_pub_key) { + EC_KEY* key = EC_KEY_new_by_curve_name(NID_secp256k1); + if (!key) { + std::cerr << "Failed to create EC_KEY object" << std::endl; + return nullptr; + } + + EC_POINT* pub_point = EC_POINT_new(EC_KEY_get0_group(key)); + if (!EC_POINT_oct2point(EC_KEY_get0_group(key), pub_point, compressed_pub_key, 33, nullptr)) { + std::cerr << "Failed to convert compressed public key" << std::endl; + EC_POINT_free(pub_point); + EC_KEY_free(key); + return nullptr; + } + + if (!EC_KEY_set_public_key(key, pub_point)) { + std::cerr << "Failed to set public key" << std::endl; + EC_POINT_free(pub_point); + EC_KEY_free(key); + return nullptr; + } + + EC_POINT_free(pub_point); + return key; +} + +// Function to verify Ethereum-compatible signature +bool verify_ethereum_signature(const crypto::hash& m, const crypto::eth_signature& sig_res, const crypto::eth_public_key& compressed_pub_key) { + EC_KEY* ec_key = create_ec_key_from_compressed_public_key((const unsigned char*)&compressed_pub_key.data[0]); + if (!ec_key) { + throw std::runtime_error("Failed to create EC key from compressed public key"); + } + const unsigned char* r = (unsigned char*)&sig_res.data[0]; + const unsigned char* s = (unsigned char*)&sig_res.data[32]; + const unsigned char* hash = (unsigned char*)&m; + + // Create ECDSA_SIG from r and s + BIGNUM* bn_r = BN_bin2bn(r, 32, nullptr); + BIGNUM* bn_s = BN_bin2bn(s, 32, nullptr); + if (!bn_r || !bn_s) { + EC_KEY_free(ec_key); + BN_free(bn_r); + BN_free(bn_s); + throw std::runtime_error("Failed to convert r or s to BIGNUM"); + } + + ECDSA_SIG* sig = ECDSA_SIG_new(); + if (!sig) { + EC_KEY_free(ec_key); + BN_free(bn_r); + BN_free(bn_s); + throw std::runtime_error("Failed to create ECDSA_SIG object"); + } + + if (!ECDSA_SIG_set0(sig, bn_r, bn_s)) { + EC_KEY_free(ec_key); + ECDSA_SIG_free(sig); + BN_free(bn_r); + BN_free(bn_s); + throw std::runtime_error("Failed to set r and s in ECDSA_SIG"); + } + + // Verify the signature + int verification_result = ECDSA_do_verify(hash, 32, sig, ec_key); + + ECDSA_SIG_free(sig); + EC_KEY_free(ec_key); + + + return verification_result == 1; +} + + +// +// struct KeyPair { +// std::vector private_key; // 32 bytes +// std::vector public_key; // 33 bytes (compressed format) +// }; + +// Function to generate an Ethereum-compatible key pair +bool generate_ethereum_key_pair(crypto::eth_secret_key& sec_key, crypto::eth_public_key& pub_key) { + /*KeyPair keypair;*/ + + // Create a new EC_KEY object with the secp256k1 curve + EC_KEY* key = EC_KEY_new_by_curve_name(NID_secp256k1); + if (!key) { + throw std::runtime_error("Failed to create new EC_KEY object"); + } + + // Generate the key pair + if (EC_KEY_generate_key(key) == 0) { + EC_KEY_free(key); + throw std::runtime_error("Failed to generate key pair"); + } + + // Extract the private key + const BIGNUM* priv_bn = EC_KEY_get0_private_key(key); + if (!priv_bn) { + EC_KEY_free(key); + throw std::runtime_error("Failed to get private key"); + } + + BN_bn2binpad(priv_bn, (unsigned char*)&sec_key.data[0], 32); + + // Extract the public key in compressed format + const EC_POINT* pub_point = EC_KEY_get0_public_key(key); + if (!pub_point) { + EC_KEY_free(key); + throw std::runtime_error("Failed to get public key"); + } + + //keypair.public_key.resize(33); // Compressed format + if (EC_POINT_point2oct(EC_KEY_get0_group(key), pub_point, POINT_CONVERSION_COMPRESSED, + (unsigned char*)&pub_key.data[0], sizeof(pub_key.data), nullptr) == 0) { + EC_KEY_free(key); + throw std::runtime_error("Failed to convert public key to compressed format"); + } + + EC_KEY_free(key); + return true; +} + + + + + +namespace crypto +{ + bool generate_eth_key_pair(eth_secret_key& sec_key, eth_public_key& pub_key) noexcept + { + try + { +#ifndef USE_OPEN_SSL_FOR_ECDSA + secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){ + secp256k1_context_destroy(ctx); + ctx = nullptr; + }); + + uint8_t randomness[32]; + crypto::generate_random_bytes(sizeof randomness, randomness); + if (!secp256k1_context_randomize(ctx, randomness)) + return false; + + for(size_t i = 1024; i != 0; --i) + { + crypto::generate_random_bytes(sizeof sec_key, sec_key.data); + if (secp256k1_ec_seckey_verify(ctx, sec_key.data)) + break; + if (i == 1) + return false; + } + + secp256k1_pubkey uncompressed_pub_key{}; + if (!secp256k1_ec_pubkey_create(ctx, &uncompressed_pub_key, sec_key.data)) + return false; + + size_t output_len = sizeof pub_key; + if (!secp256k1_ec_pubkey_serialize(ctx, pub_key.data, &output_len, &uncompressed_pub_key, SECP256K1_EC_COMPRESSED)) + return false; + + return true; +#else + return generate_ethereum_key_pair(sec_key, pub_key); +#endif + } + catch(...) + { + return false; + } + } + +#ifndef USE_OPEN_SSL_FOR_ECDSA + bool eth_secret_key_to_public_key(const eth_secret_key& sec_key, eth_public_key& pub_key) noexcept + { + try + { + // TODO: do we need this? consider using static context + secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){ + secp256k1_context_destroy(ctx); + ctx = nullptr; + }); + + secp256k1_pubkey uncompressed_pub_key{}; + if (!secp256k1_ec_pubkey_create(ctx, &uncompressed_pub_key, sec_key.data)) + return false; + + size_t output_len = sizeof pub_key; + if (!secp256k1_ec_pubkey_serialize(ctx, pub_key.data, &output_len, &uncompressed_pub_key, SECP256K1_EC_COMPRESSED)) + return false; + + return true; + } + catch(...) + { + return false; + } + } +#endif + // generates secp256k1 ECDSA signature + bool generate_eth_signature(const hash& m, const eth_secret_key& sec_key, eth_signature& sig) noexcept + { + try + { +#ifndef USE_OPEN_SSL_FOR_ECDSA + secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){ + secp256k1_context_destroy(ctx); + ctx = nullptr; + }); + + uint8_t randomness[32]; + crypto::generate_random_bytes(sizeof randomness, randomness); + if (!secp256k1_context_randomize(ctx, randomness)) + return false; + + secp256k1_ecdsa_signature secp256k1_ecdsa_sig{}; + if (!secp256k1_ecdsa_sign(ctx, &secp256k1_ecdsa_sig, (const unsigned char*)m.data, sec_key.data, NULL, NULL)) + return false; + + if (!secp256k1_ecdsa_signature_serialize_compact(ctx, sig.data, &secp256k1_ecdsa_sig)) + return false; + + return true; +#else + return generate_ethereum_signature((const unsigned char*)&m.data, (unsigned char*)&sec_key.data, sig); +#endif + } + catch(...) + { + return false; + } + } + + // verifies secp256k1 ECDSA signature + bool verify_eth_signature(const hash& m, const eth_public_key& pub_key, const eth_signature& sig) noexcept + { + try + { + // TODO (performance) consider using secp256k1_context_static for verification -- sowle +#ifndef USE_OPEN_SSL_FOR_ECDSA + secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){ + secp256k1_context_destroy(ctx); + ctx = nullptr; + }); + + uint8_t randomness[32]; + crypto::generate_random_bytes(sizeof randomness, randomness); + if (!secp256k1_context_randomize(ctx, randomness)) + return false; + + secp256k1_ecdsa_signature secp256k1_ecdsa_sig{}; + secp256k1_pubkey uncompressed_pub_key{}; + + if (!secp256k1_ecdsa_signature_parse_compact(ctx, &secp256k1_ecdsa_sig, sig.data)) + return false; + + if (!secp256k1_ec_pubkey_parse(ctx, &uncompressed_pub_key, pub_key.data, sizeof pub_key)) + return false; + + // verify a signature + if (!secp256k1_ecdsa_verify(ctx, &secp256k1_ecdsa_sig, (const unsigned char*)m.data, &uncompressed_pub_key)) + return false; + + return true; +#else + return verify_ethereum_signature(m, sig, pub_key); +#endif + } + catch(...) + { + return false; + } + } + + std::ostream& operator<<(std::ostream& o, const eth_secret_key& v) + { + return o << epee::string_tools::pod_to_hex(v); + } + + std::ostream& operator<<(std::ostream& o, const eth_public_key& v) + { + return o << epee::string_tools::pod_to_hex(v); + } + std::ostream& operator<<(std::ostream& o, const eth_signature& v) + { + return o << epee::string_tools::pod_to_hex(v); + } + + +} // namespace crypto diff --git a/src/crypto/eth_signature.h b/src/crypto/eth_signature.h new file mode 100644 index 00000000..4db0b8e7 --- /dev/null +++ b/src/crypto/eth_signature.h @@ -0,0 +1,67 @@ +// Copyright (c) 2024 Zano Project +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#pragma once +#include +#include +#include "hash.h" + +namespace crypto +{ + + // secp256k1 public key in serialized (compressed) form that is used in Etherium + struct eth_public_key + { + uint8_t data[33]; + }; + + // secp256k1 secret key + struct eth_secret_key + { + uint8_t data[32]; + }; + + // secp256k1 ECDSA signature is serialized (compressed) form that is used in Etherium + struct eth_signature + { + uint8_t data[64]; + }; + + // generates secp256k1 keypair + bool generate_eth_key_pair(eth_secret_key& sec_key, eth_public_key& pub_key) noexcept; + + // converts eth_secret_key to eth_public_key + //bool _eth_secret_key_to_public_key(const eth_secret_key& sec_key, eth_public_key& pub_key) noexcept; + + // generates secp256k1 ECDSA signature + bool generate_eth_signature(const hash& m, const eth_secret_key& sec_key, eth_signature& sig) noexcept; + + // verifies secp256k1 ECDSA signature + bool verify_eth_signature(const hash& m, const eth_public_key& pub_key, const eth_signature& sig) noexcept; + + + inline bool operator==(const eth_public_key& lhs, const eth_public_key& rhs) + { + return memcmp(lhs.data, rhs.data, sizeof lhs.data) == 0; + } + + inline bool operator!=(const eth_public_key& lhs, const eth_public_key& rhs) + { + return !(lhs == rhs); + } + + inline bool operator==(const eth_secret_key& lhs, const eth_secret_key& rhs) + { + return memcmp(lhs.data, rhs.data, sizeof lhs.data) == 0; + } + + inline bool operator!=(const eth_secret_key& lhs, const eth_secret_key& rhs) + { + return !(lhs == rhs); + } + + std::ostream& operator<<(std::ostream& o, const eth_secret_key& v); + std::ostream& operator<<(std::ostream& o, const eth_public_key& v); + std::ostream& operator<<(std::ostream& o, const eth_signature& v); + +} // namespace crypto diff --git a/src/crypto/one_out_of_many_proofs.cpp b/src/crypto/one_out_of_many_proofs.cpp index a30374c1..ab921f44 100644 --- a/src/crypto/one_out_of_many_proofs.cpp +++ b/src/crypto/one_out_of_many_proofs.cpp @@ -1,10 +1,11 @@ -// Copyright (c) 2023 Zano Project -// Copyright (c) 2023 sowle (val@zano.org, crypto.sowle@gmail.com) +// Copyright (c) 2023-2024 Zano Project +// Copyright (c) 2023-2024 sowle (val@zano.org, crypto.sowle@gmail.com) // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. // #include "one_out_of_many_proofs.h" #include "../currency_core/crypto_config.h" +#include "../currency_core/currency_config.h" // for static asset checks #include "epee/include/misc_log_ex.h" //DISABLE_GCC_AND_CLANG_WARNING(unused-function) @@ -22,6 +23,8 @@ namespace crypto static const size_t N_max = 256; static const size_t mn_max = 16; + static_assert(CURRENCY_TX_MAX_ALLOWED_INPUTS <= N_max, "CURRENCY_TX_MAX_ALLOWED_INPUTS is inconsistent with one-out-of-many proof limits"); // TODO: consider moving this check out -- sowle + const point_t& get_BGE_generator(size_t index, bool& ok) { static std::vector precalculated_generators; diff --git a/src/crypto/random.c b/src/crypto/random.c index bdac1e7a..a54bdf74 100644 --- a/src/crypto/random.c +++ b/src/crypto/random.c @@ -43,7 +43,7 @@ void generate_system_random_bytes(size_t n, void *result) { void generate_system_random_bytes(size_t n, void *result) { int fd; if ((fd = open("/dev/urandom", O_RDONLY | O_NOCTTY | O_CLOEXEC)) < 0) { - err(EXIT_FAILURE, "open /dev/urandom"); + exit(EXIT_FAILURE); } for (;;) { ssize_t res = read(fd, result, n); @@ -52,17 +52,17 @@ void generate_system_random_bytes(size_t n, void *result) { } if (res < 0) { if (errno != EINTR) { - err(EXIT_FAILURE, "read /dev/urandom"); + exit(EXIT_FAILURE); } } else if (res == 0) { - errx(EXIT_FAILURE, "read /dev/urandom: end of file"); + exit(EXIT_FAILURE); } else { result = padd(result, (size_t) res); n -= (size_t) res; } } if (close(fd) < 0) { - err(EXIT_FAILURE, "close /dev/urandom"); + exit(EXIT_FAILURE); } } diff --git a/src/crypto/zarcanum.h b/src/crypto/zarcanum.h index 3fe4b5c1..55e6b202 100644 --- a/src/crypto/zarcanum.h +++ b/src/crypto/zarcanum.h @@ -123,6 +123,15 @@ namespace crypto return generate_schnorr_sig(m, point_t(A), scalar_t(secret_a), result); } + inline bool generate_schnorr_sig(const hash& m, const secret_key& secret_a, generic_schnorr_sig& result) + { + scalar_t secret_a_s(secret_a); + if (!secret_a_s.is_reduced()) + return false; + point_t A = secret_a_s * c_point_G; + return generate_schnorr_sig_custom_generator(m, A, secret_a_s, result, c_point_G); + } + template inline bool verify_schnorr_sig(const hash& m, const public_key& A, const generic_schnorr_sig& sig) noexcept; diff --git a/src/currency_core/account.cpp b/src/currency_core/account.cpp index def20255..ff194a59 100644 --- a/src/currency_core/account.cpp +++ b/src/currency_core/account.cpp @@ -60,7 +60,7 @@ namespace currency return m_keys; } //----------------------------------------------------------------- - void crypt_with_pass(const void* scr_data, std::size_t src_length, void* dst_data, const std::string& password) + void account_base::crypt_with_pass(const void* scr_data, std::size_t src_length, void* dst_data, const std::string& password) { crypto::chacha8_key key = AUTO_VAL_INIT(key); crypto::generate_chacha8_key(password, key); @@ -71,28 +71,36 @@ namespace currency crypto::chacha8(scr_data, src_length, key, iv, (char*)dst_data); } //----------------------------------------------------------------- - std::string account_base::get_seed_phrase(const std::string& password) const + std::string account_base::get_seed_phrase(const std::string& password) const { if (m_keys_seed_binary.empty()) return ""; + return get_seed_phrase(password, m_keys_seed_binary); + } + //----------------------------------------------------------------- + std::string account_base::get_seed_phrase(const std::string& password, const std::vector& keys_seed_binary) const + { + if (keys_seed_binary.empty()) + return ""; - std::vector processed_seed_binary = m_keys_seed_binary; + std::vector processed_seed_binary = keys_seed_binary; if (!password.empty()) { + CHECK_AND_ASSERT_THROW_MES(currency::validate_password(password), "seed phrase password contains invalid characters, seed phrase cannot be created with such a password"); //encrypt seed phrase binary data - crypt_with_pass(&m_keys_seed_binary[0], m_keys_seed_binary.size(), &processed_seed_binary[0], password); + crypt_with_pass(&keys_seed_binary[0], keys_seed_binary.size(), &processed_seed_binary[0], password); } std::string keys_seed_text = tools::mnemonic_encoding::binary2text(processed_seed_binary); - std::string timestamp_word = currency::get_word_from_timstamp(m_creation_timestamp, !password.empty()); + std::string timestamp_word = currency::get_word_from_timestamp(m_creation_timestamp, !password.empty()); // floor creation time to WALLET_BRAIN_DATE_QUANTUM to make checksum calculation stable bool self_check_is_password_used = false; - uint64_t creation_timestamp_rounded = get_timstamp_from_word(timestamp_word, self_check_is_password_used); + uint64_t creation_timestamp_rounded = get_timestamp_from_word(timestamp_word, self_check_is_password_used); CHECK_AND_ASSERT_THROW_MES(self_check_is_password_used == !password.empty(), "Account seed phrase internal error: password flag encoded wrong"); constexpr uint16_t checksum_max = tools::mnemonic_encoding::NUMWORDS >> 1; // maximum value of checksum - std::string binary_for_check_sum((const char*)&m_keys_seed_binary[0], m_keys_seed_binary.size()); + std::string binary_for_check_sum((const char*)&keys_seed_binary[0], keys_seed_binary.size()); binary_for_check_sum.append(password); crypto::hash h = crypto::cn_fast_hash(binary_for_check_sum.data(), binary_for_check_sum.size()); *reinterpret_cast(&h) = creation_timestamp_rounded; @@ -100,6 +108,9 @@ namespace currency uint64_t h_64 = *reinterpret_cast(&h); uint16_t checksum = h_64 % (checksum_max + 1); + if (checksum == checksum_max) // workaround for incorrect checksum calculation (trying to keep the whole scheme untouched) -- sowle + checksum = 0; + uint8_t auditable_flag = 0; if (m_keys.account_address.flags & ACCOUNT_PUBLIC_ADDRESS_FLAG_AUDITABLE) auditable_flag = 1; @@ -127,11 +138,12 @@ namespace currency return true; } //----------------------------------------------------------------- - bool account_base::restore_from_seed_phrase(const std::string& seed_phrase, const std::string& seed_password) + bool account_base::restore_from_seed_phrase(const std::string& seed_phrase_, const std::string& seed_password) { //cut the last timestamp word from restore_dats std::list words; - boost::split(words, seed_phrase, boost::is_space()); + std::string seed_phrase = epee::string_tools::trim(seed_phrase_); + boost::split(words, seed_phrase, boost::is_space(), boost::token_compress_on); std::string keys_seed_text, timestamp_word, auditable_flag_and_checksum_word; if (words.size() == SEED_PHRASE_V1_WORDS_COUNT) @@ -176,7 +188,7 @@ namespace currency bool has_password = false; try { - m_creation_timestamp = get_timstamp_from_word(timestamp_word, has_password); + m_creation_timestamp = get_timestamp_from_word(timestamp_word, has_password); } catch (...) { @@ -207,6 +219,10 @@ namespace currency h = crypto::cn_fast_hash(&h, sizeof h); uint64_t h_64 = *reinterpret_cast(&h); uint16_t checksum_calculated = h_64 % (checksum_max + 1); + + if (checksum_calculated == checksum_max) // workaround for incorrect checksum calculation (trying to keep the whole scheme untouched) -- sowle + checksum_calculated = 0; + if (checksum != checksum_calculated) { LOG_PRINT_L0("seed phase has invalid checksum: " << checksum_calculated << ", while " << checksum << " is expected, check your words"); @@ -221,6 +237,8 @@ namespace currency if (auditable_flag) m_keys.account_address.flags |= ACCOUNT_PUBLIC_ADDRESS_FLAG_AUDITABLE; + else + m_keys.account_address.flags &= ~ACCOUNT_PUBLIC_ADDRESS_FLAG_AUDITABLE; return true; } @@ -230,11 +248,13 @@ namespace currency return seed_phrase.find(':') != std::string::npos; } //----------------------------------------------------------------- - bool account_base::is_seed_password_protected(const std::string& seed_phrase, bool& is_password_protected) + bool account_base::is_seed_password_protected(const std::string& seed_phrase_, bool& is_password_protected) { //cut the last timestamp word from restore_dats std::list words; - boost::split(words, seed_phrase, boost::is_space()); + + std::string seed_phrase = epee::string_tools::trim(seed_phrase_); + boost::split(words, seed_phrase, boost::is_space(), boost::token_compress_on); //let's validate each word for (const auto& w: words) @@ -260,7 +280,7 @@ namespace currency return false; } - get_timstamp_from_word(timestamp_word, is_password_protected); + get_timestamp_from_word(timestamp_word, is_password_protected); return true; } diff --git a/src/currency_core/account.h b/src/currency_core/account.h index 37a0c953..d732181d 100644 --- a/src/currency_core/account.h +++ b/src/currency_core/account.h @@ -56,6 +56,7 @@ namespace currency std::string get_public_address_str() const; std::string get_seed_phrase(const std::string& seed_password) const; + std::string get_seed_phrase(const std::string& password, const std::vector& keys_seed_binary) const; std::string get_tracking_seed() const; bool restore_from_seed_phrase(const std::string& seed_phrase, const std::string& seed_password); bool restore_from_tracking_seed(const std::string& tracking_seed); @@ -82,6 +83,8 @@ namespace currency static std::vector string_to_vector_of_chars(const std::string& v) { return std::vector(v.begin(), v.end()); } static bool is_seed_password_protected(const std::string& seed_phrase, bool& is_password_protected); static bool is_seed_tracking(const std::string& seed_phrase); + static void crypt_with_pass(const void* scr_data, std::size_t src_length, void* dst_data, const std::string& password); + BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(m_keys) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index 419cdd1c..0d6d41f8 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -830,7 +830,7 @@ bool blockchain_storage::purge_transaction_from_blockchain(const crypto::hash& t fee = get_tx_fee(tx_res_ptr->tx); purge_transaction_keyimages_from_blockchain(tx, true); - bool r = unprocess_blockchain_tx_extra(tx); + bool r = unprocess_blockchain_tx_extra(tx, tx_res_ptr->m_keeper_block_height); CHECK_AND_ASSERT_MES(r, false, "failed to unprocess_blockchain_tx_extra for tx " << tx_id); r = unprocess_blockchain_tx_attachments(tx, get_current_blockchain_size(), 0/*TODO: add valid timestamp here in future if need*/); @@ -1553,8 +1553,20 @@ bool blockchain_storage::create_block_template(const create_block_template_param } diffic = get_next_diff_conditional(pos); + CHECK_AND_ASSERT_MES(diffic, false, "get_next_diff_conditional failed"); + + // check PoW block timestamp against the current blockchain timestamp median -- if it's not okay, don't create a new block + // TODO (performance) both get_next_diff_conditional and get_last_n_blocks_timestamps obtains last N blocks, consider data reusing -- sowle + if (!pos && !params.ignore_pow_ts_check) + { + uint64_t median_ts = get_last_n_blocks_timestamps_median(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW); + if(b.timestamp < median_ts) + { + LOG_PRINT_YELLOW("Block template construction failed because current core timestamp, " << b.timestamp << ", is less than median of last " << BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW << " blocks, " << median_ts, LOG_LEVEL_2); + return false; + } + } - CHECK_AND_ASSERT_MES(diffic, false, "difficulty owverhead."); @@ -3805,7 +3817,7 @@ bool blockchain_storage::pop_transaction_from_global_index(const transaction& tx return true; } //------------------------------------------------------------------ -bool blockchain_storage::unprocess_blockchain_tx_extra(const transaction& tx) +bool blockchain_storage::unprocess_blockchain_tx_extra(const transaction& tx, const uint64_t height) { tx_extra_info ei = AUTO_VAL_INIT(ei); bool r = parse_and_validate_tx_extra(tx, ei); @@ -3818,9 +3830,7 @@ bool blockchain_storage::unprocess_blockchain_tx_extra(const transaction& tx) if (ei.m_asset_operation.operation_type != ASSET_DESCRIPTOR_OPERATION_UNDEFINED) { - crypto::public_key asset_id = currency::null_pkey; - CHECK_AND_ASSERT_MES(get_or_calculate_asset_id(ei.m_asset_operation, nullptr, &asset_id), false, "get_or_calculate_asset_id failed"); - r = pop_asset_info(asset_id); + r = pop_asset_info(ei.m_asset_operation, height); CHECK_AND_ASSERT_MES(r, false, "failed to pop_alias_info"); } return true; @@ -3862,15 +3872,16 @@ bool blockchain_storage::get_asset_info(const crypto::public_key& asset_id, asse { CRITICAL_REGION_LOCAL(m_read_lock); auto as_ptr = m_db_assets.find(asset_id); - if (as_ptr) - { - if (as_ptr->size()) - { - result = as_ptr->back().descriptor; - return true; - } - } - return false; + if (!as_ptr) + return false; + if (as_ptr->empty()) + return false; + // the last history item must have opt_descriptor, but we check it just to be sure + if (!as_ptr->back().opt_descriptor.has_value()) + return false; + + result = as_ptr->back().opt_descriptor.get(); + return true; } //------------------------------------------------------------------ uint64_t blockchain_storage::get_assets(uint64_t offset, uint64_t count, std::list& assets) const @@ -3880,19 +3891,21 @@ uint64_t blockchain_storage::get_assets(uint64_t offset, uint64_t count, std::li m_db_assets.enumerate_items([&](uint64_t i, const crypto::public_key& asset_id, const std::list& asset_descriptor_history) { if (i < offset) - { - return true; - } + return true; // continue + + CHECK_AND_ASSERT_THROW_MES(asset_descriptor_history.size(), "unexpectedly, the asset_descriptor_history is empty; asset_id: " << asset_id); + CHECK_AND_ASSERT_THROW_MES(asset_descriptor_history.back().opt_descriptor.has_value(), "the last element of asset_descriptor_history doesn't have descriptor; asset_id: " << asset_id); + asset_descriptor_with_id& added_item = assets.emplace_back(); + asset_descriptor_base& added_item_adb = static_cast(added_item); + added_item_adb = asset_descriptor_history.back().opt_descriptor.get(); + replace_asset_ticker_and_full_name_if_invalid(added_item_adb, asset_id); + added_item.asset_id = asset_id; - CHECK_AND_ASSERT_THROW_MES(asset_descriptor_history.size(), "asset_descriptor_history unexpectedly have 0 size"); - assets.push_back(asset_descriptor_with_id()); - static_cast(assets.back()) = asset_descriptor_history.back().descriptor; - assets.back().asset_id = asset_id; if (assets.size() >= count) { - return false; + return false; // stop } - return true; + return true; // continue }); return assets.size(); } @@ -4010,7 +4023,8 @@ bool blockchain_storage::put_alias_info(const transaction & tx, extra_alias_entr //@@ remove get_tx_fee_median(); LOG_PRINT_MAGENTA("[ALIAS_REGISTERED]: " << ai.m_alias << ": " << get_account_address_as_str(ai.m_address) << ", fee median: " << get_tx_fee_median(), LOG_LEVEL_1); rise_core_event(CORE_EVENT_ADD_ALIAS, alias_info_to_rpc_alias_info(ai)); - }else + } + else { //update procedure CHECK_AND_ASSERT_MES(ai.m_sign.size() == 1, false, "alias " << ai.m_alias << " can't be update, wrong ai.m_sign.size() count: " << ai.m_sign.size()); @@ -4085,133 +4099,399 @@ bool blockchain_storage::put_alias_info(const transaction & tx, extra_alias_entr return true; } //------------------------------------------------------------------ -bool blockchain_storage::pop_asset_info(const crypto::public_key& asset_id) +bool blockchain_storage::pop_asset_info(const asset_descriptor_operation& ado, const uint64_t height) { CRITICAL_REGION_LOCAL(m_read_lock); + crypto::public_key asset_id = currency::null_pkey; + CHECK_AND_ASSERT_MES(get_or_calculate_asset_id(ado, nullptr, &asset_id), false, "get_or_calculate_asset_id failed"); + auto asset_history_ptr = m_db_assets.find(asset_id); CHECK_AND_ASSERT_MES(asset_history_ptr && asset_history_ptr->size(), false, "empty name list in pop_asset_info"); - + assets_container::t_value_type local_asset_hist = *asset_history_ptr; - local_asset_hist.pop_back(); + + if (is_hardfork_active_for_height(ZANO_HARDFORK_05, height)) + { + // NEW HF5 handling + assets_container::t_value_type local_asset_hist = *asset_history_ptr; + asset_descriptor_operation& last_ado = local_asset_hist.back(); // above we made sure that the history isn't empty + CHECK_AND_ASSERT_MES(last_ado.opt_descriptor.has_value(), false, "opt_descriptor is missing during asset pop, op: " << (int)ado.operation_type); + asset_descriptor_base& last_adb = last_ado.opt_descriptor.get(); + + if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT) + { + // just change the most recent history record, don't pop + if (last_adb.hidden_supply) + { + //CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (emit)"); + //last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) - crypto::point_t(ado.opt_amount_commitment.get())).to_public_key(); + return false; // not supported atm + } + else + { + CHECK_AND_ASSERT_MES(ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (emit)"); + last_adb.current_supply -= ado.opt_amount.get(); + } + } + else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) + { + // just change the most recent history record, don't pop + if (last_adb.hidden_supply) + { + //CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (burn)"); + //last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) + crypto::point_t(ado.opt_amount_commitment.get())).to_public_key(); + return false; // not supported atm + } + else + { + CHECK_AND_ASSERT_MES(ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (burn)"); + last_adb.current_supply += ado.opt_amount.get(); + } + } + else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) + { + // just pop the most recent history record + local_asset_hist.pop_back(); + } + else + { + CHECK_AND_ASSERT_MES(false, false, "invalid operation: " << (int)ado.operation_type); + } + } + else + { + // HF4 + local_asset_hist.pop_back(); + } + + // both HF4 and HF5 if (local_asset_hist.size()) m_db_assets.set(asset_id, local_asset_hist); else m_db_assets.erase(asset_id); - LOG_PRINT_MAGENTA("[ASSET_POP]: " << asset_id << ": " << (!local_asset_hist.empty() ? "(prev)" : "(erased)"), LOG_LEVEL_1); + return true; } //------------------------------------------------------------------ -bool validate_ado_ownership(asset_op_verification_context& avc) +bool blockchain_storage::validate_ado_ownership(asset_op_verification_context& avc) const { - asset_operation_ownership_proof aoop = AUTO_VAL_INIT(aoop); - bool r = get_type_in_variant_container(avc.tx.proofs, aoop); - CHECK_AND_ASSERT_MES(r, false, "Ownership validation failed - missing signature (asset_operation_ownership_proof)"); + bool r = false; + CHECK_AND_ASSERT_MES(avc.asset_op_history->size() != 0, false, "asset with id " << avc.asset_id << " has empty history record"); + const asset_descriptor_operation& last_ado = avc.asset_op_history->back(); + CHECK_AND_ASSERT_MES(last_ado.opt_descriptor.has_value(), false, "last ado is missing opt_descriptor; asset id: " << avc.asset_id); + const asset_descriptor_base& last_adb = last_ado.opt_descriptor.get(); - CHECK_AND_ASSERT_MES(avc.asset_op_history->size() != 0, false, "asset with id " << avc.asset_id << " has invalid history size() == 0"); + if (is_hardfork_active_for_height(ZANO_HARDFORK_05, avc.height)) + { + if (last_adb.owner_eth_pub_key.has_value()) + { + CHECK_AND_ASSERT_MES(last_adb.owner == null_pkey, false, "owner_eth_pub_key is set but owner pubkey is nonzero"); + asset_operation_ownership_proof_eth aoop_eth{}; + r = get_type_in_variant_container(avc.tx.proofs, aoop_eth); + CHECK_AND_ASSERT_MES(r, false, "Ownership validation failed: asset_operation_ownership_proof_eth is missing"); + if (!crypto::verify_eth_signature(avc.tx_id, last_adb.owner_eth_pub_key.value(), aoop_eth.eth_sig)) + { + LOG_ERROR("Failed to validate secp256k1 signature for hash: " << avc.tx_id << ", signature: " << aoop_eth.eth_sig); + return false; + } + else + { + return true; + } + } + // owner_eth_pub_key has no value -- fallback to default + } - crypto::public_key owner_key = avc.asset_op_history->back().descriptor.owner; - return crypto::verify_schnorr_sig(avc.tx_id, owner_key, aoop.gss); + asset_operation_ownership_proof aoop{}; + r = get_type_in_variant_container(avc.tx.proofs, aoop); + CHECK_AND_ASSERT_MES(r, false, "Ownership validation failed: asset_operation_ownership_proof is missing"); + + return crypto::verify_schnorr_sig(avc.tx_id, last_adb.owner, aoop.gss); } //------------------------------------------------------------------ -bool blockchain_storage::validate_asset_operation_against_current_blochain_state(asset_op_verification_context& avc) const +bool blockchain_storage::validate_asset_operation_hf4(asset_op_verification_context& avc) const { CRITICAL_REGION_LOCAL(m_read_lock); + CHECK_AND_ASSERT_MES(!is_hardfork_active_for_height(ZANO_HARDFORK_05, avc.height), false, "validate_asset_operation_hf4 was called in HF5"); CHECK_AND_ASSERT_MES(get_or_calculate_asset_id(avc.ado, &avc.asset_id_pt, &avc.asset_id), false, "get_or_calculate_asset_id failed"); avc.asset_op_history = m_db_assets.find(avc.asset_id); const asset_descriptor_operation& ado = avc.ado; + bool need_to_validate_ao_amount_commitment = true; + if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER) { CHECK_AND_ASSERT_MES(!avc.asset_op_history, false, "asset with id " << avc.asset_id << " has already been registered"); - avc.amount_to_validate = ado.descriptor.current_supply; - CHECK_AND_ASSERT_MES(validate_asset_operation_amount_commitment(avc), false, "validate_asset_operation_amount_commitment failed!"); - if(this->is_hardfork_active(ZANO_HARDFORK_05)) - { - CHECK_AND_ASSERT_MES(validate_ado_initial(ado.descriptor), false, "validate_ado_initial failed!"); - } + CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing while registering asset " << avc.asset_id); + + // CZ please review the following two added lines, do we need them _here_? I think they should go to hardfork_specific_terms... + // anyway, we musn't miss these checks for the sake of consensus + CHECK_AND_ASSERT_MES(!ado.opt_descriptor.get().owner_eth_pub_key.has_value(), false, "owner_eth_pub_key is prohibited before HF5"); + CHECK_AND_ASSERT_MES(!ado.opt_asset_id_salt.has_value(), false, "opt_asset_id_salt is prohibited before HF5"); + + CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing while registering asset " << avc.asset_id); + avc.amount_to_validate = ado.opt_descriptor.get().current_supply; } else { - CHECK_AND_ASSERT_MES(avc.asset_op_history && avc.asset_op_history->size(), false, "asset with id " << avc.asset_id << " has not been registered"); + CHECK_AND_ASSERT_MES(avc.asset_op_history && avc.asset_op_history->size() > 0, false, "asset with id " << avc.asset_id << " has not been registered"); + const asset_descriptor_operation& last_ado = avc.asset_op_history->back(); + CHECK_AND_ASSERT_MES(last_ado.opt_descriptor.has_value(), false, "opt_descriptor is missing in last ado, op: " << (int)ado.operation_type); + const asset_descriptor_base& last_adb = last_ado.opt_descriptor.get(); + CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing in ado, op: " << (int)ado.operation_type); + const asset_descriptor_base adb = ado.opt_descriptor.get(); + // check ownership permission - if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE /*|| ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN*/) + if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) { bool r = validate_ado_ownership(avc); - CHECK_AND_ASSERT_MES(r, false, "Faild to validate ownership of asset_descriptor_operation, rejecting"); + CHECK_AND_ASSERT_MES(r, false, "Failed to validate ownership of asset_descriptor_operation, rejecting"); } avc.amount_to_validate = 0; - bool need_to_validate_balance_proof = true; if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) { //check that total current_supply haven't changed - CHECK_AND_ASSERT_MES(ado.descriptor.current_supply == avc.asset_op_history->back().descriptor.current_supply, false, "update operation attempted to change emission, failed"); - CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, avc.asset_op_history->back().descriptor), false, "update operation attempted to change fileds that shouldn't be modified, failed"); - need_to_validate_balance_proof = false; + CHECK_AND_ASSERT_MES(adb.current_supply == last_adb.current_supply, false, "update operation attempted to change emission, failed"); + CHECK_AND_ASSERT_MES(validate_ado_update_allowed(adb, last_adb), false, "update operation modifies asset descriptor in a prohibited manner"); + need_to_validate_ao_amount_commitment = false; } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT) { - CHECK_AND_ASSERT_MES(ado.descriptor.current_supply > avc.asset_op_history->back().descriptor.current_supply, false, "emit operation does not increase the current supply, failed"); - CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, avc.asset_op_history->back().descriptor), false, "emit operation is not allowed to update fields"); - CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == avc.asset_op_history->back().descriptor.meta_info, false, "emit operation is not allowed to update meta info"); - avc.amount_to_validate = ado.descriptor.current_supply - avc.asset_op_history->back().descriptor.current_supply; + CHECK_AND_ASSERT_MES(adb.current_supply > last_adb.current_supply, false, "emit operation does not increase the current supply, failed"); + CHECK_AND_ASSERT_MES(validate_ado_update_allowed(adb, last_adb), false, "emit operation modifies asset descriptor in a prohibited manner"); + CHECK_AND_ASSERT_MES(adb.meta_info == last_adb.meta_info, false, "emit operation is not allowed to update meta info"); + avc.amount_to_validate = adb.current_supply - last_adb.current_supply; } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) { - CHECK_AND_ASSERT_MES(ado.descriptor.current_supply < avc.asset_op_history->back().descriptor.current_supply, false, "burn operation does not decrease the current supply, failed"); - CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, avc.asset_op_history->back().descriptor), false, "burn operation is not allowed to update fields"); - CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == avc.asset_op_history->back().descriptor.meta_info, false, "burn operation is not allowed to update meta info"); - avc.amount_to_validate = avc.asset_op_history->back().descriptor.current_supply - ado.descriptor.current_supply; + CHECK_AND_ASSERT_MES(adb.current_supply < last_adb.current_supply, false, "burn operation does not decrease the current supply, failed"); + CHECK_AND_ASSERT_MES(validate_ado_update_allowed(adb, last_adb), false, "burn operation modifies asset descriptor in a prohibited manner"); + CHECK_AND_ASSERT_MES(adb.meta_info == last_adb.meta_info, false, "burn operation is not allowed to update meta info"); + avc.amount_to_validate = last_adb.current_supply - adb.current_supply; } else { LOG_ERROR("Unknown operation type: " << (int)ado.operation_type); return false; } + } - if (need_to_validate_balance_proof) - { - bool r = validate_asset_operation_amount_commitment(avc); - CHECK_AND_ASSERT_MES(r, false, "Balance proof validation failed for asset_descriptor_operation"); - } + if (need_to_validate_ao_amount_commitment) + { + bool r = validate_asset_operation_amount_commitment(avc); + CHECK_AND_ASSERT_MES(r, false, "Balance proof validation failed for asset_descriptor_operation"); } return true; } //------------------------------------------------------------------ -bool blockchain_storage::put_asset_info(const transaction& tx, const crypto::hash& tx_id, const asset_descriptor_operation& ado) +bool blockchain_storage::validate_asset_operation_hf5(asset_op_verification_context& avc) const +{ + CRITICAL_REGION_LOCAL(m_read_lock); + CHECK_AND_ASSERT_MES(is_hardfork_active_for_height(ZANO_HARDFORK_05, avc.height), false, "validate_asset_operation was called before HF5"); + + CHECK_AND_ASSERT_MES(get_or_calculate_asset_id(avc.ado, &avc.asset_id_pt, &avc.asset_id), false, "get_or_calculate_asset_id failed"); + avc.asset_op_history = m_db_assets.find(avc.asset_id); + + const asset_descriptor_operation& ado = avc.ado; + + bool need_to_validate_ao_amount_commitment = true; + + if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER) + { + CHECK_AND_ASSERT_MES(!avc.asset_op_history, false, "asset with id " << avc.asset_id << " has already been registered"); + CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing while registering asset " << avc.asset_id); + avc.amount_to_validate = ado.opt_descriptor.get().current_supply; + // HF5 specific + CHECK_AND_ASSERT_MES(validate_ado_initial(ado.opt_descriptor.get()), false, "validate_ado_initial failed!"); + CHECK_AND_ASSERT_MES(ado.opt_amount.has_value() && avc.amount_to_validate == ado.opt_amount.get(), false, "opt_amount is missing or incorrect"); + } + else + { + CHECK_AND_ASSERT_MES(avc.asset_op_history && avc.asset_op_history->size() > 0, false, "asset with id " << avc.asset_id << " has not been registered"); + const asset_descriptor_operation& last_ado = avc.asset_op_history->back(); + CHECK_AND_ASSERT_MES(last_ado.opt_descriptor.has_value(), false, "opt_descriptor is missing in last ado, op: " << (int)ado.operation_type); + const asset_descriptor_base& last_adb = last_ado.opt_descriptor.get(); + + // check ownership permission + if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) + { + bool r = validate_ado_ownership(avc); + CHECK_AND_ASSERT_MES(r, false, "Failed to validate ownership of asset_descriptor_operation, rejecting"); + } + + avc.amount_to_validate = 0; + if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) + { + CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing (update)"); + //check that total current_supply haven't changed + CHECK_AND_ASSERT_MES(ado.opt_descriptor.get().current_supply == last_adb.current_supply, false, "update operation attempted to change emission, failed"); + CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.opt_descriptor.get(), last_adb), false, "update operation modifies asset descriptor in a prohibited manner"); + need_to_validate_ao_amount_commitment = false; + } + else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT) + { + //CHECK_AND_ASSERT_MES(ado.descriptor.current_supply > last_adb.current_supply, false, "emit operation does not increase the current supply, failed"); + //CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_adb), false, "emit operation modifies asset descriptor in a prohibited manner"); + //CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == last_adb.meta_info, false, "emit operation is not allowed to update meta info"); + + if (!last_adb.hidden_supply) + { + CHECK_AND_ASSERT_MES(ado.opt_amount.has_value(), false, "opt_amount is missing (emit)"); + uint64_t amount = ado.opt_amount.get(); + + CHECK_AND_ASSERT_MES(last_adb.current_supply + amount >= last_adb.current_supply, false, "current_supply overflow: " << last_adb.current_supply << ", amount: " << amount << " (emit)"); + CHECK_AND_ASSERT_MES(last_adb.current_supply + amount <= last_adb.total_max_supply, false, "current_supply overflow: " << last_adb.current_supply << ", amount: " << amount << ", max supply: " << last_adb.total_max_supply << " (emit)"); + avc.amount_to_validate = amount; + } + } + else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) + { + //CHECK_AND_ASSERT_MES(ado.descriptor.current_supply < last_adb.current_supply, false, "burn operation does not decrease the current supply, failed"); + //CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_adb), false, "burn operation modifies asset descriptor in a prohibited manner"); + //CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == last_adb.meta_info, false, "burn operation is not allowed to update meta info"); + + if (!last_adb.hidden_supply) + { + CHECK_AND_ASSERT_MES(ado.opt_amount.has_value(), false, "opt_amount is missing (burn)"); + uint64_t amount = ado.opt_amount.get(); + + CHECK_AND_ASSERT_MES(last_adb.current_supply - amount <= last_adb.current_supply, false, "current_supply overflow: " << last_adb.current_supply << ", amount: " << amount << " (burn)"); + avc.amount_to_validate = amount; + } + } + else + { + LOG_ERROR("Unknown operation type: " << (int)ado.operation_type); + return false; + } + } + + if (need_to_validate_ao_amount_commitment) + { + bool r = validate_asset_operation_amount_commitment(avc); + CHECK_AND_ASSERT_MES(r, false, "Balance proof validation failed for asset_descriptor_operation"); + } + + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::validate_asset_operation(asset_op_verification_context& avc, uint64_t height) const +{ + if (is_hardfork_active_for_height(ZANO_HARDFORK_05, height)) + { + return validate_asset_operation_hf5(avc); + } + else + { + return validate_asset_operation_hf4(avc); + } +} +//------------------------------------------------------------------ +bool blockchain_storage::put_asset_info(const transaction& tx, const crypto::hash& tx_id, const asset_descriptor_operation& ado, const uint64_t height) { CRITICAL_REGION_LOCAL(m_read_lock); - asset_op_verification_context avc = { tx, tx_id, ado }; - CHECK_AND_ASSERT_MES(validate_asset_operation_against_current_blochain_state(avc), false, "asset operation validation failed"); + asset_op_verification_context avc = { tx, tx_id, ado, height }; + CHECK_AND_ASSERT_MES(validate_asset_operation(avc, height), false, "asset operation validation failed"); - assets_container::t_value_type local_asset_history{}; - if (avc.asset_op_history) - local_asset_history = *avc.asset_op_history; - local_asset_history.push_back(ado); - m_db_assets.set(avc.asset_id, local_asset_history); - - switch(ado.operation_type) + if (is_hardfork_active_for_height(ZANO_HARDFORK_05, height)) { - case ASSET_DESCRIPTOR_OPERATION_REGISTER: - LOG_PRINT_MAGENTA("[ASSET_REGISTERED]: " << print_money_brief(ado.descriptor.current_supply, ado.descriptor.decimal_point) << ", " << avc.asset_id << ": " << ado.descriptor.ticker << ", \"" << ado.descriptor.full_name << "\"", LOG_LEVEL_1); - break; - case ASSET_DESCRIPTOR_OPERATION_UPDATE: - LOG_PRINT_MAGENTA("[ASSET_UPDATED]: " << avc.asset_id << ": " << ado.descriptor.ticker << ", \"" << ado.descriptor.full_name << "\"", LOG_LEVEL_1); - break; - case ASSET_DESCRIPTOR_OPERATION_EMIT: - LOG_PRINT_MAGENTA("[ASSET_EMITTED]: " << print_money_brief(avc.amount_to_validate, ado.descriptor.decimal_point) << ", " << avc.asset_id << ": " << ado.descriptor.ticker << ", \"" << ado.descriptor.full_name << "\"", LOG_LEVEL_1); - break; - case ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN: - LOG_PRINT_MAGENTA("[ASSET_BURNT]: " << print_money_brief(avc.amount_to_validate, ado.descriptor.decimal_point) << ", " << avc.asset_id << ": " << ado.descriptor.ticker << ", \"" << ado.descriptor.full_name << "\"", LOG_LEVEL_1); - break; - default: - LOG_ERROR("Unknown operation type: " << (int)ado.operation_type); + // NEW HF5 handling here + + assets_container::t_value_type local_asset_history{}; + if (avc.asset_op_history) + local_asset_history = *avc.asset_op_history; + + if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER) + { + CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing (register)"); // validated in validate_asset_operation(), just in case + const asset_descriptor_base& adb = ado.opt_descriptor.get(); + local_asset_history.push_back(ado); + LOG_PRINT_MAGENTA("[ASSET_REGISTERED]: " << print_money_brief(adb.current_supply, adb.decimal_point) << ", " << avc.asset_id << ": " << adb.ticker << ", \"" << adb.full_name << "\"", LOG_LEVEL_1); + } + else + { + CHECK_AND_ASSERT_MES(!local_asset_history.empty(), false, "local_asset_history is empty (update/emit/burn)"); + asset_descriptor_operation& last_ado = local_asset_history.back(); + CHECK_AND_ASSERT_MES(last_ado.opt_descriptor.has_value(), false, "last_ado.opt_descriptor is missing (update/emit/burn)"); + asset_descriptor_base& last_adb = local_asset_history.back().opt_descriptor.get(); + + if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) + { + local_asset_history.push_back(ado); + LOG_PRINT_MAGENTA("[ASSET_UPDATED]: " << avc.asset_id << ": " << last_adb.ticker << ", \"" << last_adb.full_name << "\"", LOG_LEVEL_1); + } + else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT) + { + // just change the most recent history record, don't push + if (last_adb.hidden_supply) + { + //CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (emit)"); + //last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) + crypto::point_t(ado.opt_amount_commitment.get())).to_public_key(); + return false; // not supported atm + } + else + { + CHECK_AND_ASSERT_MES(ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (emit)"); + last_adb.current_supply += ado.opt_amount.get(); + } + LOG_PRINT_MAGENTA("[ASSET_EMITTED]: " << print_money_brief(avc.amount_to_validate, last_adb.decimal_point) << ", " << avc.asset_id << ": " << last_adb.ticker << ", \"" << last_adb.full_name << "\"", LOG_LEVEL_1); + } + else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) + { + // just change the most recent history record, don't push + if (last_adb.hidden_supply) + { + //CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (burn)"); + //last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) - crypto::point_t(ado.opt_amount_commitment.get())).to_public_key(); + return false; // not supported atm + } + else + { + CHECK_AND_ASSERT_MES(ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (burn)"); + last_adb.current_supply -= ado.opt_amount.get(); + } + LOG_PRINT_MAGENTA("[ASSET_BURNT]: " << print_money_brief(avc.amount_to_validate, last_adb.decimal_point) << ", " << avc.asset_id << ": " << last_adb.ticker << ", \"" << last_adb.full_name << "\"", LOG_LEVEL_1); + } + else + CHECK_AND_ASSERT_MES(false, false, "Unknown operation type: " << (int)ado.operation_type); + } + + m_db_assets.set(avc.asset_id, local_asset_history); + } + else + { + // HF4 + assets_container::t_value_type local_asset_history{}; + if (avc.asset_op_history) + local_asset_history = *avc.asset_op_history; + local_asset_history.push_back(ado); + m_db_assets.set(avc.asset_id, local_asset_history); + + const asset_descriptor_base& adb = ado.opt_descriptor.get(); // in HF4 descriptor must always be present, validated in validate_asset_operation_hf4() + switch(ado.operation_type) + { + case ASSET_DESCRIPTOR_OPERATION_REGISTER: + LOG_PRINT_MAGENTA("[ASSET_REGISTERED]: " << print_money_brief(adb.current_supply, adb.decimal_point) << ", " << avc.asset_id << ": " << adb.ticker << ", \"" << adb.full_name << "\"", LOG_LEVEL_1); + break; + case ASSET_DESCRIPTOR_OPERATION_UPDATE: + LOG_PRINT_MAGENTA("[ASSET_UPDATED]: " << avc.asset_id << ": " << adb.ticker << ", \"" << adb.full_name << "\"", LOG_LEVEL_1); + break; + case ASSET_DESCRIPTOR_OPERATION_EMIT: + LOG_PRINT_MAGENTA("[ASSET_EMITTED]: " << print_money_brief(avc.amount_to_validate, adb.decimal_point) << ", " << avc.asset_id << ": " << adb.ticker << ", \"" << adb.full_name << "\"", LOG_LEVEL_1); + break; + case ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN: + LOG_PRINT_MAGENTA("[ASSET_BURNT]: " << print_money_brief(avc.amount_to_validate, adb.decimal_point) << ", " << avc.asset_id << ": " << adb.ticker << ", \"" << adb.full_name << "\"", LOG_LEVEL_1); + break; + default: + LOG_ERROR("Unknown operation type: " << (int)ado.operation_type); + } } return true; @@ -4287,7 +4567,7 @@ bool blockchain_storage::prevalidate_alias_info(const transaction& tx, const ext return true; } //------------------------------------------------------------------ -bool blockchain_storage::process_blockchain_tx_extra(const transaction& tx, const crypto::hash& tx_id) +bool blockchain_storage::process_blockchain_tx_extra(const transaction& tx, const crypto::hash& tx_id, const uint64_t height) { //check transaction extra tx_extra_info ei = AUTO_VAL_INIT(ei); @@ -4303,7 +4583,7 @@ bool blockchain_storage::process_blockchain_tx_extra(const transaction& tx, cons } if (ei.m_asset_operation.operation_type != ASSET_DESCRIPTOR_OPERATION_UNDEFINED) { - r = put_asset_info(tx, tx_id, ei.m_asset_operation); + r = put_asset_info(tx, tx_id, ei.m_asset_operation, height); CHECK_AND_ASSERT_MES(r, false, "failed to put_asset_info"); } @@ -4503,7 +4783,7 @@ bool blockchain_storage::add_transaction_from_block(const transaction& tx, const CHECK_AND_ASSERT_MES(validate_tx_for_hardfork_specific_terms(tx, tx_id, bl_height), false, "tx " << tx_id << ": hardfork-specific validation failed"); TIME_MEASURE_START_PD(tx_process_extra); - bool r = process_blockchain_tx_extra(tx, tx_id); + bool r = process_blockchain_tx_extra(tx, tx_id, bl_height); CHECK_AND_ASSERT_MES(r, false, "failed to process_blockchain_tx_extra"); TIME_MEASURE_FINISH_PD_COND(need_to_profile, tx_process_extra); @@ -4519,7 +4799,7 @@ bool blockchain_storage::add_transaction_from_block(const transaction& tx, const { LOG_ERROR("critical internal error: add_transaction_input_visitor failed. but key_images should be already checked"); purge_transaction_keyimages_from_blockchain(tx, false); - bool r = unprocess_blockchain_tx_extra(tx); + bool r = unprocess_blockchain_tx_extra(tx, bl_height); CHECK_AND_ASSERT_MES(r, false, "failed to unprocess_blockchain_tx_extra"); unprocess_blockchain_tx_attachments(tx, bl_height, timestamp); @@ -4540,7 +4820,7 @@ bool blockchain_storage::add_transaction_from_block(const transaction& tx, const { LOG_ERROR("critical internal error: tx with id: " << tx_id << " in block id: " << bl_id << " already in blockchain"); purge_transaction_keyimages_from_blockchain(tx, true); - bool r = unprocess_blockchain_tx_extra(tx); + bool r = unprocess_blockchain_tx_extra(tx, bl_height); CHECK_AND_ASSERT_MES(r, false, "failed to unprocess_blockchain_tx_extra"); unprocess_blockchain_tx_attachments(tx, bl_height, timestamp); @@ -5429,7 +5709,8 @@ uint64_t blockchain_storage::get_last_n_blocks_timestamps_median(size_t n) const std::vector timestamps = get_last_n_blocks_timestamps(n); uint64_t median_res = epee::misc_utils::median(timestamps); - m_timestamps_median_cache[n] = median_res; + if (timestamps.size() == n) + m_timestamps_median_cache[n] = median_res; return median_res; } //------------------------------------------------------------------ @@ -5803,6 +6084,7 @@ bool blockchain_storage::validate_tx_for_hardfork_specific_terms(const transacti bool var_is_after_hardfork_2_zone = m_core_runtime_config.is_hardfork_active_for_height(2, block_height); bool var_is_after_hardfork_3_zone = m_core_runtime_config.is_hardfork_active_for_height(3, block_height); bool var_is_after_hardfork_4_zone = m_core_runtime_config.is_hardfork_active_for_height(4, block_height); + bool var_is_after_hardfork_5_zone = m_core_runtime_config.is_hardfork_active_for_height(5, block_height); auto is_allowed_before_hardfork1 = [&](const auto& el) -> bool { @@ -5932,8 +6214,22 @@ bool blockchain_storage::validate_tx_for_hardfork_specific_terms(const transacti LOG_ERROR("asset_descriptor_operation not allowed in tx with TX_FLAG_SIGNATURE_MODE_SEPARATE"); return false; } - } + + if (var_is_after_hardfork_5_zone) + { + // additional checks here + } + else + { + if (count_type_in_variant_container(tx.proofs) != 0) + { + LOG_ERROR("asset_operation_ownership_proof_eth is not allowed prior to HF5"); + return false; + } + } + + return true; } //------------------------------------------------------------------ @@ -6930,7 +7226,7 @@ bool blockchain_storage::is_hardfork_active(size_t hardfork_id) const //------------------------------------------------------------------ bool blockchain_storage::is_hardfork_active_for_height(size_t hardfork_id, uint64_t height) const { - return m_core_runtime_config.is_hardfork_active_for_height(hardfork_id, height); + return m_core_runtime_config.is_hardfork_active_for_height(hardfork_id, height != UINT64_MAX ? height : m_db_blocks.size()); } //------------------------------------------------------------------ bool blockchain_storage::prevalidate_block(const block& bl) diff --git a/src/currency_core/blockchain_storage.h b/src/currency_core/blockchain_storage.h index 2c6e0bb5..78c75e66 100644 --- a/src/currency_core/blockchain_storage.h +++ b/src/currency_core/blockchain_storage.h @@ -380,7 +380,10 @@ namespace currency bool for_altchain, const alt_chain_type& alt_chain = alt_chain_type(), uint64_t split_height = 0)const; - bool validate_asset_operation_against_current_blochain_state(asset_op_verification_context& avc) const; + bool validate_ado_ownership(asset_op_verification_context& avc) const; + bool validate_asset_operation_hf4(asset_op_verification_context& avc) const; + bool validate_asset_operation_hf5(asset_op_verification_context& avc) const; + bool validate_asset_operation(asset_op_verification_context& avc, uint64_t height) const; void set_core_runtime_config(const core_runtime_config& pc) const; const core_runtime_config& get_core_runtime_config()const; @@ -678,14 +681,14 @@ namespace currency uint64_t get_adjusted_time()const; bool complete_timestamps_vector(uint64_t start_height, std::vector& timestamps); bool update_next_comulative_size_limit(); - bool process_blockchain_tx_extra(const transaction& tx, const crypto::hash& tx_id); - bool unprocess_blockchain_tx_extra(const transaction& tx); + bool process_blockchain_tx_extra(const transaction& tx, const crypto::hash& tx_id, const uint64_t height); + bool unprocess_blockchain_tx_extra(const transaction& tx, const uint64_t height); bool process_blockchain_tx_attachments(const transaction& tx, uint64_t h, const crypto::hash& bl_id, uint64_t timestamp); bool unprocess_blockchain_tx_attachments(const transaction& tx, uint64_t h, uint64_t timestamp); bool pop_alias_info(const extra_alias_entry& ai); bool put_alias_info(const transaction& tx, extra_alias_entry& ai); - bool pop_asset_info(const crypto::public_key& asset_id); - bool put_asset_info(const transaction& tx, const crypto::hash& tx_id, const asset_descriptor_operation& ado); + bool pop_asset_info(const asset_descriptor_operation& ado, const uint64_t height); + bool put_asset_info(const transaction& tx, const crypto::hash& tx_id, const asset_descriptor_operation& ado, const uint64_t height); void fill_addr_to_alias_dict(); //bool resync_spent_tx_flags(); bool prune_ring_signatures_and_attachments_if_need(); diff --git a/src/currency_core/blockchain_storage_basic.h b/src/currency_core/blockchain_storage_basic.h index 0013dc51..c15cdbe9 100644 --- a/src/currency_core/blockchain_storage_basic.h +++ b/src/currency_core/blockchain_storage_basic.h @@ -136,6 +136,7 @@ namespace currency account_public_address stakeholder_address; blobdata ex_nonce; bool pos = false; + bool ignore_pow_ts_check = false; pos_entry pe; std::list explicit_txs; fill_block_template_func_t *pcustom_fill_block_template_func; diff --git a/src/currency_core/connection_context.h b/src/currency_core/connection_context.h index 6c242cc1..1320619c 100644 --- a/src/currency_core/connection_context.h +++ b/src/currency_core/connection_context.h @@ -51,6 +51,8 @@ namespace currency uint64_t m_last_response_height; int64_t m_time_delta; std::string m_remote_version; + int m_build_number = 0; + private: template friend class t_currency_protocol_handler; uncopybale_currency_context m_priv; diff --git a/src/currency_core/core_runtime_config.h b/src/currency_core/core_runtime_config.h index c1ba86af..799ba0f9 100644 --- a/src/currency_core/core_runtime_config.h +++ b/src/currency_core/core_runtime_config.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020 Zano Project +// Copyright (c) 2014-2024 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Boolberry developers // Distributed under the MIT/X11 software license, see the accompanying @@ -109,10 +109,16 @@ namespace currency wide_difficulty_type max_pos_difficulty; hard_forks_descriptor hard_forks; + std::array min_build_numbers_for_hard_forks; - bool is_hardfork_active_for_height(size_t hardfork_id, uint64_t height) const + bool is_hardfork_active_for_height(size_t hardfork_id, uint64_t upcoming_block_height) const { - return hard_forks.is_hardfork_active_for_height(hardfork_id, height); + return hard_forks.is_hardfork_active_for_height(hardfork_id, upcoming_block_height); + } + + int get_min_allowed_build_version_for_height(uint64_t upcoming_block_height) const + { + return min_build_numbers_for_hard_forks[hard_forks.get_the_most_recent_hardfork_id_for_height(upcoming_block_height)]; } static uint64_t _default_core_time_function() @@ -123,7 +129,7 @@ namespace currency inline core_runtime_config get_default_core_runtime_config() { - core_runtime_config pc = AUTO_VAL_INIT(pc); + core_runtime_config pc{}; pc.min_coinstake_age = POS_MINIMUM_COINSTAKE_AGE; pc.pos_minimum_heigh = POS_START_HEIGHT; pc.tx_pool_min_fee = TX_MINIMUM_FEE; @@ -137,7 +143,9 @@ namespace currency pc.hard_forks.set_hardfork_height(2, ZANO_HARDFORK_02_AFTER_HEIGHT); pc.hard_forks.set_hardfork_height(3, ZANO_HARDFORK_03_AFTER_HEIGHT); pc.hard_forks.set_hardfork_height(4, ZANO_HARDFORK_04_AFTER_HEIGHT); - + pc.hard_forks.set_hardfork_height(5, ZANO_HARDFORK_05_AFTER_HEIGHT); pc.min_build_numbers_for_hard_forks[5] = ZANO_HARDFORK_05_MIN_BUILD_VER; + static_assert(5 + 1 == ZANO_HARDFORKS_TOTAL); + pc.get_core_time = &core_runtime_config::_default_core_time_function; bool r = epee::string_tools::hex_to_pod(ALIAS_SHORT_NAMES_VALIDATION_PUB_KEY, pc.alias_validation_pubkey); CHECK_AND_ASSERT_THROW_MES(r, "failed to parse alias_validation_pub_key"); diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index 0d3e5891..d0630089 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -40,6 +40,7 @@ #include "crypto/hash.h" #include "crypto/range_proofs.h" #include "crypto/zarcanum.h" +#include "crypto/eth_signature.h" #include "misc_language.h" #include "block_flags.h" #include "etc_custom_serialization.h" @@ -73,7 +74,9 @@ namespace currency /************************************************************************/ /* */ /************************************************************************/ - + + struct asset_descriptor_operation_v1; + //since structure used in blockchain as a key accessor, then be sure that there is no padding inside #pragma pack(push, 1) struct account_public_address_old @@ -695,6 +698,21 @@ namespace currency } }; +#define ASSET_DESCRIPTOR_BASE_HF4_VER 0 +#define ASSET_DESCRIPTOR_BASE_HF5_VER 2 +#define ASSET_DESCRIPTOR_BASE_LAST_VER 2 + + struct dummy + { + BEGIN_SERIALIZE() + END_SERIALIZE() + + BEGIN_BOOST_SERIALIZATION() + END_BOOST_SERIALIZATION_TOTAL_FIELDS(0) + }; + + typedef boost::variant asset_descriptor_base_etc_fields; + typedef boost::variant asset_owner_pub_key_v; struct asset_descriptor_base { @@ -706,9 +724,14 @@ namespace currency std::string meta_info; crypto::public_key owner = currency::null_pkey; // consider premultipling by 1/8 bool hidden_supply = false; - uint8_t version = 0; + uint8_t version = ASSET_DESCRIPTOR_BASE_HF4_VER; + //version 1 members + boost::optional owner_eth_pub_key; // note: the size is 33 bytes (if present) // NOTE: using boost::optional instead of std::optional because of the Boost compilation issue: https://github.com/boostorg/serialization/issues/319 -- sowle + //version 2 members + std::vector etc; //container for future use if we would be adding some optional parameters that is not known yet, but without mess related to format version - BEGIN_VERSIONED_SERIALIZE(0, version) + + BEGIN_VERSIONED_SERIALIZE(ASSET_DESCRIPTOR_BASE_LAST_VER, version) FIELD(total_max_supply) FIELD(current_supply) FIELD(decimal_point) @@ -717,9 +740,13 @@ namespace currency FIELD(meta_info) FIELD(owner) FIELD(hidden_supply) + END_VERSION_UNDER(1) + FIELD(owner_eth_pub_key) + END_VERSION_UNDER(2) + FIELD(etc) END_SERIALIZE() - + BOOST_SERIALIZATION_CURRENT_ARCHIVE_VER(2) BEGIN_BOOST_SERIALIZATION() BOOST_SERIALIZE(total_max_supply) BOOST_SERIALIZE(current_supply) @@ -729,17 +756,23 @@ namespace currency BOOST_SERIALIZE(meta_info) BOOST_SERIALIZE(owner) BOOST_SERIALIZE(hidden_supply) - END_BOOST_SERIALIZATION() + BOOST_END_VERSION_UNDER(1) + BOOST_SERIALIZE(owner_eth_pub_key) + BOOST_END_VERSION_UNDER(2) + BOOST_SERIALIZE(etc) + BOOST_SERIALIZE(version) + END_BOOST_SERIALIZATION_TOTAL_FIELDS(11) BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(total_max_supply) DOC_DSCR("Maximum possible supply for given asset, can't be changed after deployment") DOC_EXMP(1000000000000000000) DOC_END - KV_SERIALIZE(current_supply) DOC_DSCR("Currently emitted supply for given asset (ignored for REGISTER operation)") DOC_EXMP(500000000000000000) DOC_END - KV_SERIALIZE(decimal_point) DOC_DSCR("Decimal point") DOC_EXMP(12) DOC_END - KV_SERIALIZE(ticker) DOC_DSCR("Ticker associated with asset") DOC_EXMP("ZUSD") DOC_END - KV_SERIALIZE(full_name) DOC_DSCR("Full name of the asset") DOC_EXMP("Zano wrapped USD") DOC_END - KV_SERIALIZE(meta_info) DOC_DSCR("Any other information assetiaded with asset in a free form") DOC_EXMP("Stable and private") DOC_END - KV_SERIALIZE_POD_AS_HEX_STRING(owner) DOC_DSCR("Owner's key, used only for EMIT and UPDATE validation, could be changed by transferring asset ownership") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END - KV_SERIALIZE(hidden_supply) DOC_DSCR("This one reserved for future use, will be documented later") DOC_END + KV_SERIALIZE(total_max_supply) DOC_DSCR("Maximum possible supply for a given asset, cannot be changed after deployment.") DOC_EXMP(1000000000000000000) DOC_END + KV_SERIALIZE(current_supply) DOC_DSCR("Currently emitted supply for the given asset (ignored for REGISTER operation).") DOC_EXMP(500000000000000000) DOC_END + KV_SERIALIZE(decimal_point) DOC_DSCR("Decimal point.") DOC_EXMP(12) DOC_END + KV_SERIALIZE(ticker) DOC_DSCR("Ticker associated with the asset.") DOC_EXMP("ZABC") DOC_END + KV_SERIALIZE(full_name) DOC_DSCR("Full name of the asset.") DOC_EXMP("Zano wrapped ABC") DOC_END + KV_SERIALIZE(meta_info) DOC_DSCR("Any other information associated with the asset in free form.") DOC_EXMP("Stable and private") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(owner) DOC_DSCR("Owner's key, used only for EMIT and UPDATE validation, can be changed by transferring asset ownership.") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END + KV_SERIALIZE(hidden_supply) DOC_DSCR("This field is reserved for future use and will be documented later.") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(owner_eth_pub_key) DOC_DSCR("[Optional] Owner's key in the case when ETH signature is used.") DOC_END END_KV_SERIALIZE_MAP() }; @@ -768,31 +801,62 @@ namespace currency #define ASSET_DESCRIPTOR_OPERATION_UPDATE 3 #define ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN 4 -#define ASSET_DESCRIPTOR_OPERATION_STRUCTURE_VER 1 +#define ASSET_DESCRIPTOR_OPERATION_HF4_VER 1 +#define ASSET_DESCRIPTOR_OPERATION_HF5_VER 2 +#define ASSET_DESCRIPTOR_OPERATION_LAST_VER 2 + + typedef boost::variant asset_descriptor_operation_etc_fields; struct asset_descriptor_operation { - uint8_t operation_type = ASSET_DESCRIPTOR_OPERATION_UNDEFINED; - asset_descriptor_base descriptor; - crypto::public_key amount_commitment; // premultiplied by 1/8 - boost::optional opt_asset_id; // target asset_id - for update/emit - uint8_t verion = ASSET_DESCRIPTOR_OPERATION_STRUCTURE_VER; + uint8_t operation_type = ASSET_DESCRIPTOR_OPERATION_UNDEFINED; + uint8_t version = ASSET_DESCRIPTOR_OPERATION_HF4_VER; + // register emit burn update + boost::optional opt_amount_commitment; // + + + - (premultiplied by 1/8) + boost::optional opt_asset_id; // - + + + + boost::optional opt_descriptor; // + - - + + boost::optional opt_amount; // ? ? ? - (only for non-hidden supply) + boost::optional opt_asset_id_salt; // ? - - - (optional) + std::vector etc; // (reserved for future use) - BEGIN_VERSIONED_SERIALIZE(ASSET_DESCRIPTOR_OPERATION_STRUCTURE_VER, verion) + + BEGIN_VERSIONED_SERIALIZE(ASSET_DESCRIPTOR_OPERATION_LAST_VER, version) + CHAIN_TRANSITION_VER(0, asset_descriptor_operation_v1) + CHAIN_TRANSITION_VER(1, asset_descriptor_operation_v1) FIELD(operation_type) - FIELD(descriptor) - FIELD(amount_commitment) - END_VERSION_UNDER(1) + FIELD(opt_amount_commitment) FIELD(opt_asset_id) + FIELD(opt_descriptor) + FIELD(opt_amount) + FIELD(opt_asset_id_salt) + FIELD(etc) END_SERIALIZE() + BOOST_SERIALIZATION_CURRENT_ARCHIVE_VER(2) BEGIN_BOOST_SERIALIZATION() + BOOST_CHAIN_TRANSITION_VER(1, asset_descriptor_operation_v1) + BOOST_CHAIN_TRANSITION_VER(0, asset_descriptor_operation_v1) + BOOST_SERIALIZE(version) BOOST_SERIALIZE(operation_type) - BOOST_SERIALIZE(descriptor) - BOOST_SERIALIZE(amount_commitment) - BOOST_END_VERSION_UNDER(1) + BOOST_SERIALIZE(opt_amount_commitment) BOOST_SERIALIZE(opt_asset_id) - END_BOOST_SERIALIZATION() + BOOST_SERIALIZE(opt_descriptor) + BOOST_SERIALIZE(opt_amount) + BOOST_SERIALIZE(opt_asset_id_salt) + BOOST_SERIALIZE(etc) + END_BOOST_SERIALIZATION_TOTAL_FIELDS(8) + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(version) DOC_DSCR("Asset operation type struct version") DOC_EXMP(2) DOC_END + KV_SERIALIZE(operation_type) DOC_DSCR("Asset operation type identifier") DOC_EXMP(1) DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(opt_amount_commitment) DOC_DSCR("(optional) Asset operation amount commitment (register/emit/burn).") DOC_EXMP("5688b56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(opt_asset_id) DOC_DSCR("(optional) ID of an asset (emit/burn/update).") DOC_EXMP("cc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6") DOC_END + KV_SERIALIZE(opt_descriptor) DOC_DSCR("(optional) Asset operation descriptor (register/update).") DOC_EXMP_AUTO() DOC_END + KV_SERIALIZE(opt_amount) DOC_DSCR("(optional) Asset operation amount (register/emit/burn when supply is non-hidden).") DOC_EXMP_AUTO() DOC_END + KV_SERIALIZE(opt_asset_id_salt) DOC_DSCR("(optional) Asset ID salt. May only be used for asset registration.") DOC_EXMP_AUTO() DOC_END + //KV_SERIALIZE(etc) DOC_DSCR("Extra operations") DOC_EXMP_AUTO() DOC_END <---- serialization for variant not supported yet + END_KV_SERIALIZE_MAP() + }; struct asset_operation_proof @@ -807,6 +871,7 @@ namespace currency FIELD(opt_amount_commitment_g_proof) END_SERIALIZE() + BOOST_SERIALIZATION_CURRENT_ARCHIVE_VER(1) BEGIN_BOOST_SERIALIZATION() BOOST_SERIALIZE(opt_amount_commitment_composition_proof) BOOST_SERIALIZE(opt_amount_commitment_g_proof) @@ -825,6 +890,7 @@ namespace currency FIELD(gss) END_SERIALIZE() + BOOST_SERIALIZATION_CURRENT_ARCHIVE_VER(1) BEGIN_BOOST_SERIALIZATION() BOOST_SERIALIZE(gss) BOOST_END_VERSION_UNDER(1) @@ -833,6 +899,26 @@ namespace currency }; + struct asset_operation_ownership_proof_eth + { + crypto::eth_signature eth_sig; // 64 bytes + uint8_t version = 0; + + BEGIN_VERSIONED_SERIALIZE(0, version) + FIELD(eth_sig) + END_SERIALIZE() + + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(eth_sig) + BOOST_SERIALIZE(version) + END_BOOST_SERIALIZATION() + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_POD_AS_HEX_STRING(eth_sig) DOC_DSCR("HEX-encoded ETH signature (64 bytes)") DOC_EXMP("674bb56a5b4fa562e679ccacc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6add697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END + KV_SERIALIZE(version) DOC_DSCR("Structure version") DOC_EXMP(0) DOC_END + END_KV_SERIALIZE_MAP() + }; + struct extra_padding { std::vector buff; //stub @@ -936,7 +1022,7 @@ namespace currency typedef boost::variant signature_v; - typedef boost::variant proof_v; + typedef boost::variant proof_v; //include backward compatibility defintions @@ -986,6 +1072,19 @@ namespace currency FIELD(signatures) FIELD(proofs) END_SERIALIZE() + + + BOOST_SERIALIZATION_CURRENT_ARCHIVE_VER(0) + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(version) + BOOST_SERIALIZE(vin) + BOOST_SERIALIZE(vout) + BOOST_SERIALIZE(extra) + BOOST_SERIALIZE(signatures) + BOOST_SERIALIZE(attachment) + BOOST_END_VERSION_UNDER(1) + BOOST_SERIALIZE(proofs) + END_BOOST_SERIALIZATION() }; @@ -1121,9 +1220,12 @@ BLOB_SERIALIZER(currency::txout_to_key); VARIANT_TAG(json_archive, type_name, json_tag) -BOOST_CLASS_VERSION(currency::asset_descriptor_operation, 1); -BOOST_CLASS_VERSION(currency::asset_operation_proof, 1); -BOOST_CLASS_VERSION(currency::asset_operation_ownership_proof, 1); + +LOOP_BACK_BOOST_SERIALIZATION_VERSION(currency::asset_descriptor_operation); +LOOP_BACK_BOOST_SERIALIZATION_VERSION(currency::asset_descriptor_base); +LOOP_BACK_BOOST_SERIALIZATION_VERSION(currency::asset_operation_proof); +LOOP_BACK_BOOST_SERIALIZATION_VERSION(currency::asset_operation_ownership_proof); +LOOP_BACK_BOOST_SERIALIZATION_VERSION(currency::transaction); // txin_v variant currency @@ -1195,6 +1297,11 @@ SET_VARIANT_TAGS(currency::zc_balance_proof, 48, "zc_balance_proof"); SET_VARIANT_TAGS(currency::asset_descriptor_operation, 49, "asset_descriptor_base"); SET_VARIANT_TAGS(currency::asset_operation_proof, 50, "asset_operation_proof"); SET_VARIANT_TAGS(currency::asset_operation_ownership_proof, 51, "asset_operation_ownership_proof"); +SET_VARIANT_TAGS(currency::asset_operation_ownership_proof_eth, 52, "asset_operation_ownership_proof_eth"); + +SET_VARIANT_TAGS(crypto::eth_public_key, 60, "eth_public_key"); +//SET_VARIANT_TAGS(crypto::eth_signature, 61, "eth_signature"); +SET_VARIANT_TAGS(currency::dummy, 62, "dummy"); diff --git a/src/currency_core/currency_basic_backward_comp.inl b/src/currency_core/currency_basic_backward_comp.inl index 7dffea0e..9dc2a903 100644 --- a/src/currency_core/currency_basic_backward_comp.inl +++ b/src/currency_core/currency_basic_backward_comp.inl @@ -116,3 +116,74 @@ bool transition_convert(const transaction_v1& from, transaction_current_t& to) } return true; } + +struct asset_descriptor_operation_v1 +{ + uint8_t operation_type = ASSET_DESCRIPTOR_OPERATION_UNDEFINED; + asset_descriptor_base descriptor; + crypto::public_key amount_commitment = currency::null_pkey; // premultiplied by 1/8 + boost::optional opt_asset_id; // target asset_id - for update/emit + uint8_t verion = ASSET_DESCRIPTOR_OPERATION_HF4_VER; + + BEGIN_SERIALIZE() + FIELD(operation_type) + FIELD(descriptor) + FIELD(amount_commitment) + //END_VERSION_UNDER(1) + FIELD(opt_asset_id) + END_SERIALIZE() + + + //this map doesn't store internal version member, but it set it by default to val "1", which then transfered via transition_convert() to destination struct + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(operation_type) + BOOST_SERIALIZE(descriptor) + BOOST_SERIALIZE(amount_commitment) + //BOOST_END_VERSION_UNDER(1) + BOOST_SERIALIZE(opt_asset_id) + END_BOOST_SERIALIZATION() +}; + +template +bool transition_convert(const asset_descriptor_operation_t& from, asset_descriptor_operation_v1& to) +{ + to.verion = from.version; + to.operation_type = from.operation_type; + if(from.opt_descriptor.has_value()) + { + to.descriptor = *from.opt_descriptor; + } + else + { + throw std::runtime_error(std::string("Unexpected: missing descriptor in from transaction_current_t")); + } + + if (from.opt_amount_commitment.has_value()) + { + to.amount_commitment = *from.opt_amount_commitment; + } + else + { + //not used over update operations //throw std::runtime_error(std::string("Unexpected: missing amount_commitment in from transaction_current_t")); + } + + to.opt_asset_id = from.opt_asset_id; + + if(from.opt_amount.has_value() || from.etc.size()) + { + throw std::runtime_error(std::string("Unexpected: opt_amount or etc have values during convention, looks like object slicing with information getting lost")); + } + + return true; +} + +template +bool transition_convert(const asset_descriptor_operation_v1& from, asset_descriptor_operation_t& to) +{ + to.operation_type = from.operation_type; + to.opt_descriptor = from.descriptor; + to.opt_amount_commitment = from.amount_commitment; + to.opt_asset_id = from.opt_asset_id; // target asset_id - for update/emit + to.version = from.verion; + return true; +} \ No newline at end of file diff --git a/src/currency_core/currency_boost_serialization.h b/src/currency_core/currency_boost_serialization.h index 5d7c40f7..c12caf25 100644 --- a/src/currency_core/currency_boost_serialization.h +++ b/src/currency_core/currency_boost_serialization.h @@ -222,16 +222,7 @@ namespace boost a & x.buff; } - template - inline void serialize(Archive &a, currency::transaction &x, const boost::serialization::version_type ver) - { - a & x.version; - a & x.vin; - a & x.vout; - a & x.extra; - a & x.signatures; - a & x.attachment; - } + template inline void serialize(Archive &a, currency::keypair &kp, const boost::serialization::version_type ver) diff --git a/src/currency_core/currency_config.h b/src/currency_core/currency_config.h index c7384510..1d6edf20 100644 --- a/src/currency_core/currency_config.h +++ b/src/currency_core/currency_config.h @@ -10,7 +10,7 @@ #ifndef TESTNET #define CURRENCY_FORMATION_VERSION 84 #else -#define CURRENCY_FORMATION_VERSION 98 +#define CURRENCY_FORMATION_VERSION 99 #endif #define CURRENCY_GENESIS_NONCE (CURRENCY_FORMATION_VERSION + 101011010121) //bender's nightmare @@ -19,6 +19,7 @@ #define CURRENCY_MAX_BLOCK_NUMBER 500000000 #define CURRENCY_MAX_BLOCK_SIZE 500000000 // block header blob limit, never used! +#define CURRENCY_TX_MAX_ALLOWED_INPUTS 256 // limited primarily by asset surjection proof #define CURRENCY_TX_MAX_ALLOWED_OUTS 2000 #define CURRENCY_TX_MIN_ALLOWED_OUTS 2 // effective starting HF4 Zarcanum #define CURRENCY_PUBLIC_ADDRESS_BASE58_PREFIX 0xc5 // addresses start with 'Zx' @@ -117,7 +118,7 @@ #define P2P_MAINTAINERS_PUB_KEY "8f138bb73f6d663a3746a542770781a09579a7b84cb4125249e95530824ee607" #define DIFFICULTY_POS_STARTER 1 #else -#define P2P_DEFAULT_PORT (11112 + CURRENCY_FORMATION_VERSION) +#define P2P_DEFAULT_PORT (11211 + CURRENCY_FORMATION_VERSION) #define RPC_DEFAULT_PORT 12111 #define STRATUM_DEFAULT_PORT 11888 #define STRARUM_DEFAULT_PORT 51113 @@ -252,30 +253,34 @@ #define BC_OFFERS_CURRENCY_MARKET_FILENAME "market.bin" -#define WALLET_FILE_SERIALIZATION_VERSION 167 +#define WALLET_FILE_SERIALIZATION_VERSION 168 #define WALLET_FILE_LAST_SUPPORTED_VERSION 165 #define CURRENT_MEMPOOL_ARCHIVE_VER (CURRENCY_FORMATION_VERSION+31) -//hard forks section #define BLOCK_MAJOR_VERSION_GENESIS 1 #define BLOCK_MINOR_VERSION_GENESIS 0 #define BLOCK_MAJOR_VERSION_INITIAL 0 + +/////// Hard forks setup ////////////////////////////// #ifndef TESTNET +// Mainnet #define ZANO_HARDFORK_01_AFTER_HEIGHT 194624 // 2019-09-21 20:25:16 #define ZANO_HARDFORK_02_AFTER_HEIGHT 999999 // 2021-04-05 09:11:45 #define ZANO_HARDFORK_03_AFTER_HEIGHT 1082577 // 2021-06-01 23:28:10 #define ZANO_HARDFORK_04_AFTER_HEIGHT 2555000 // 2024-03-21 11:49:55 -#define ZANO_HARDFORK_05_AFTER_HEIGHT 999999999999999999 #define ZANO_HARDFORK_04_TIMESTAMP_ACTUAL 1711021795ull // block 2555000, 2024-03-21 11:49:55 UTC +#define ZANO_HARDFORK_05_AFTER_HEIGHT 999999999999999999 +#define ZANO_HARDFORK_05_MIN_BUILD_VER 354 #else -/////// Zarcanum Testnet ////////////////////////////// +// Testnet #define ZANO_HARDFORK_01_AFTER_HEIGHT 0 #define ZANO_HARDFORK_02_AFTER_HEIGHT 0 #define ZANO_HARDFORK_03_AFTER_HEIGHT 0 -#define ZANO_HARDFORK_04_AFTER_HEIGHT 200 -#define ZANO_HARDFORK_05_AFTER_HEIGHT 200 -#define ZANO_HARDFORK_04_TIMESTAMP_ACTUAL 1712785801ull // block 200, 2024-04-10 21:50:01 UTC +#define ZANO_HARDFORK_04_AFTER_HEIGHT 100 +#define ZANO_HARDFORK_04_TIMESTAMP_ACTUAL 1712800000ull // block 100, 2024-00-00 00:00:00 UTC +#define ZANO_HARDFORK_05_AFTER_HEIGHT 200 +#define ZANO_HARDFORK_05_MIN_BUILD_VER 356 #endif diff --git a/src/currency_core/currency_core.cpp b/src/currency_core/currency_core.cpp index 1c15498b..8d29c38d 100644 --- a/src/currency_core/currency_core.cpp +++ b/src/currency_core/currency_core.cpp @@ -552,6 +552,7 @@ namespace currency if (hardfork_id_for_prev_block != hardfork_id_for_curr_block) { LOG_PRINT_GREEN("Hardfork " << hardfork_id_for_curr_block << " has been activated after the block at height " << h, LOG_LEVEL_0); + m_pprotocol->on_hardfork_activated(hardfork_id_for_curr_block); } } diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index afd632d7..96d55b46 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -4,7 +4,7 @@ // Copyright (c) 2012-2013 The Boolberry developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. - +#include #include "include_base_utils.h" #include #ifndef MOBILE_WALLET_BUILD @@ -116,7 +116,7 @@ namespace currency secret_index = ring.size() - 1; } - CHECK_AND_ASSERT_MES(secret_index != SIZE_MAX, false, "out #" << j << ": can't find a corresponding asset id in inputs, asset id: " << H); + CHECK_AND_ASSERT_MES(secret_index != SIZE_MAX, false, "out #" << j << ": cannot find a corresponding asset id in inputs or asset operations; asset id: " << H); result.bge_proofs.emplace_back(crypto::BGE_proof_s{}); uint8_t err = 0; @@ -197,17 +197,17 @@ namespace currency return true; } //-------------------------------------------------------------------------------- - bool generate_zc_outs_range_proof(const crypto::hash& context_hash, size_t out_index_start, const tx_generation_context& outs_gen_context, + bool generate_zc_outs_range_proof(const crypto::hash& context_hash, const tx_generation_context& outs_gen_context, const std::vector& vouts, zc_outs_range_proof& result) { size_t outs_count = outs_gen_context.amounts.size(); // TODO @#@# reconsider this check CHECK_AND_ASSERT_MES(gen_context.check_sizes(outs_count), false, ""); - CHECK_AND_ASSERT_MES(out_index_start + outs_count == vouts.size(), false, ""); + CHECK_AND_ASSERT_MES(outs_count == vouts.size(), false, ""); // prepare data for aggregation proof std::vector amount_commitments_for_rp_aggregation; // E' = amount * U + y' * G crypto::scalar_vec_t y_primes; // y' - for (size_t out_index = out_index_start, i = 0; i < outs_count; ++out_index, ++i) + for (size_t i = 0; i < outs_count; ++i) { crypto::scalar_t y_prime = crypto::scalar_t::random(); amount_commitments_for_rp_aggregation.emplace_back(outs_gen_context.amounts[i] * crypto::c_point_U + y_prime * crypto::c_point_G); // E'_j = e_j * U + y'_j * G @@ -322,7 +322,7 @@ namespace currency CHECK_AND_ASSERT_MES(ogc.asset_id_blinding_mask_x_amount_sum.is_zero(), false, "it's expected that all asset ids for this tx are obvious and thus explicit"); // because this tx has no ZC inputs => all outs clearly have native asset id CHECK_AND_ASSERT_MES(ogc.ao_amount_blinding_mask.is_zero(), false, "asset emmission is not allowed for txs without ZC inputs"); - // (sum(bare inputs' amounts) - fee) * H + sum(pseudo out amount commitments) - sum(outputs' commitments) = lin(G) + // (sum(bare inputs' amounts) - fee) * H - sum(outputs' commitments) = lin(G) crypto::point_t commitment_to_zero = (crypto::scalar_t(bare_inputs_sum) - crypto::scalar_t(fee)) * currency::native_coin_asset_id_pt - ogc.amount_commitments_sum; crypto::scalar_t secret_x = -ogc.amount_blinding_masks_sum; @@ -336,6 +336,8 @@ namespace currency { // there're ZC inputs => in main balance equation we only need to cancel out X-component, because G-component cancelled out by choosing blinding mask for the last pseudo out amount commitment + // (sum(bare inputs' amounts) - fee) * H + sum(pseudo out amount commitments) + asset_op_commitment - sum(outputs' commitments) = lin(X) + crypto::point_t commitment_to_zero = (crypto::scalar_t(bare_inputs_sum) - crypto::scalar_t(fee)) * currency::native_coin_asset_id_pt + ogc.pseudo_out_amount_commitments_sum + (ogc.ao_commitment_in_outputs ? -ogc.ao_amount_commitment : ogc.ao_amount_commitment) - ogc.amount_commitments_sum; crypto::scalar_t secret_x = ogc.real_in_asset_id_blinding_mask_x_amount_sum - ogc.asset_id_blinding_mask_x_amount_sum; @@ -472,7 +474,7 @@ namespace currency } CHECK_AND_ASSERT_MES(destinations.size() <= CURRENCY_TX_MAX_ALLOWED_OUTS || height == 0, false, "Too many outs (" << destinations.size() << ")! Miner tx can't be constructed."); - tx = AUTO_VAL_INIT_T(transaction); + // tx is not cleared intentionally to allow passing additional args in the extra/attachments tx.version = tx_version; tx_generation_context tx_gen_context{}; @@ -562,7 +564,7 @@ namespace currency // range proofs currency::zc_outs_range_proof range_proofs{}; - r = generate_zc_outs_range_proof(tx_id, 0, tx_gen_context, tx.vout, range_proofs); + r = generate_zc_outs_range_proof(tx_id, tx_gen_context, tx.vout, range_proofs); CHECK_AND_ASSERT_MES(r, false, "Failed to generate zc_outs_range_proof()"); tx.proofs.emplace_back(std::move(range_proofs)); @@ -607,7 +609,7 @@ namespace currency CHECK_AND_ASSERT_MES(count_type_in_variant_container(context.tx.proofs) == 1, false, "asset_operation_proof not present or present more than once"); const asset_operation_proof& aop = get_type_in_variant_container_by_ref(context.tx.proofs); - if (context.ado.descriptor.hidden_supply) + if (context.ado.opt_descriptor.has_value() && context.ado.opt_descriptor->hidden_supply) { CHECK_AND_ASSERT_MES(aop.opt_amount_commitment_composition_proof.has_value(), false, "opt_amount_commitment_composition_proof is absent"); // TODO @#@# if asset is hidden -- check composition proof @@ -617,7 +619,8 @@ namespace currency { // make sure that amount commitment corresponds to opt_amount_commitment_g_proof CHECK_AND_ASSERT_MES(aop.opt_amount_commitment_g_proof.has_value(), false, "opt_amount_commitment_g_proof is absent"); - crypto::point_t A = crypto::point_t(context.ado.amount_commitment).modify_mul8() - context.amount_to_validate * context.asset_id_pt; + CHECK_AND_ASSERT_MES(context.ado.opt_amount_commitment.has_value(), false, "amount_commitment is absent"); + crypto::point_t A = crypto::point_t(context.ado.opt_amount_commitment.get()).modify_mul8() - context.amount_to_validate * context.asset_id_pt; bool r = crypto::check_signature(context.tx_id, A.to_public_key(), aop.opt_amount_commitment_g_proof.get()); CHECK_AND_ASSERT_MES(r, false, "opt_amount_commitment_g_proof check failed"); @@ -673,12 +676,14 @@ namespace currency { if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT) { + CHECK_AND_ASSERT_MES(ado.opt_amount_commitment.has_value(), false, "opt_amount_commitment is missing"); // amount_commitment supposed to be validated earlier in validate_asset_operation_amount_commitment() - sum_of_pseudo_out_amount_commitments += crypto::point_t(ado.amount_commitment); // *1/8 + sum_of_pseudo_out_amount_commitments += crypto::point_t(ado.opt_amount_commitment.get()); // *1/8 } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) { - outs_commitments_sum += crypto::point_t(ado.amount_commitment); // *1/8 + CHECK_AND_ASSERT_MES(ado.opt_amount_commitment.has_value(), false, "opt_amount_commitment is missing"); + outs_commitments_sum += crypto::point_t(ado.opt_amount_commitment.get()); // *1/8 } } size_t zc_sigs_count = 0; @@ -2182,16 +2187,25 @@ namespace currency return true; } - // otherwise, calculate asset id + // otherwise, it must be a register operation + CHECK_AND_ASSERT_MES(ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER, false, "unexpected asset operation type: " << (int)ado.operation_type << ", " << get_asset_operation_type_string(ado.operation_type)); + CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing: " << (int)ado.operation_type << ", " << get_asset_operation_type_string(ado.operation_type)); + const asset_descriptor_base &adb = ado.opt_descriptor.get(); + // calculate asset id crypto::hash_helper_t::hs_t hsc; hsc.add_32_chars(CRYPTO_HDS_ASSET_ID); - hsc.add_hash(crypto::hash_helper_t::h(ado.descriptor.ticker)); - hsc.add_hash(crypto::hash_helper_t::h(ado.descriptor.full_name)); - hsc.add_hash(crypto::hash_helper_t::h(ado.descriptor.meta_info)); - hsc.add_scalar(crypto::scalar_t(ado.descriptor.total_max_supply)); - hsc.add_scalar(crypto::scalar_t(ado.descriptor.decimal_point)); - hsc.add_pub_key(ado.descriptor.owner); + hsc.add_hash(crypto::hash_helper_t::h(adb.ticker)); + hsc.add_hash(crypto::hash_helper_t::h(adb.full_name)); + hsc.add_hash(crypto::hash_helper_t::h(adb.meta_info)); + hsc.add_scalar(crypto::scalar_t(adb.total_max_supply)); + hsc.add_scalar(crypto::scalar_t(adb.decimal_point)); + hsc.add_pub_key(adb.owner); + if (adb.owner_eth_pub_key.has_value()) + hsc.add_eth_pub_key(adb.owner_eth_pub_key.value()); + if (ado.opt_asset_id_salt.has_value()) + hsc.add_scalar(crypto::scalar_t(ado.opt_asset_id_salt.get())); + crypto::hash h = hsc.calc_hash_no_reduce(); // this hash function needs to be computationally expensive (s.a. the whitepaper) @@ -2236,15 +2250,11 @@ namespace currency { if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER) { - //crypto::secret_key asset_control_key{}; - //bool r = derive_key_pair_from_key_pair(sender_account_keys.account_address.spend_public_key, tx_key.sec, asset_control_key, ado.descriptor.owner, CRYPTO_HDS_ASSET_CONTROL_KEY); - //CHECK_AND_ASSERT_MES(r, false, "derive_key_pair_from_key_pair failed"); - // - // old: - // asset_control_key = Hs(CRYPTO_HDS_ASSET_CONTROL_KEY, 8 * tx_key.sec * sender_account_keys.account_address.spend_public_key, 0) - // ado.descriptor.owner = asset_control_key * G + CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing (register)"); + asset_descriptor_base& adb = ado.opt_descriptor.get(); - ado.descriptor.owner = sender_account_keys.account_address.spend_public_key; + if (!adb.owner_eth_pub_key.has_value()) + adb.owner = sender_account_keys.account_address.spend_public_key; CHECK_AND_ASSERT_MES(get_or_calculate_asset_id(ado, &gen_context.ao_asset_id_pt, &gen_context.ao_asset_id), false, "get_or_calculate_asset_id failed"); @@ -2261,10 +2271,14 @@ namespace currency amount_of_emitted_asset += item.amount; } } - ado.descriptor.current_supply = amount_of_emitted_asset; // TODO: consider setting current_supply beforehand, not setting it hear in ad-hoc manner -- sowle + adb.current_supply = amount_of_emitted_asset; + if (ado.version >= ASSET_DESCRIPTOR_BASE_HF5_VER) + { + ado.opt_amount = amount_of_emitted_asset; // TODO: support hidden supply -- sowle + } gen_context.ao_amount_commitment = amount_of_emitted_asset * gen_context.ao_asset_id_pt + gen_context.ao_amount_blinding_mask * crypto::c_point_G; - ado.amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key(); + ado.opt_amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key(); } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) { @@ -2274,7 +2288,7 @@ namespace currency gen_context.ao_amount_blinding_mask = crypto::hash_helper_t::hs(CRYPTO_HDS_ASSET_CONTROL_ABM, tx_key.sec); gen_context.ao_commitment_in_outputs = true; - // set correct asset_id to the corresponding destination entries + // calculate the amount of asset being burnt using asset_id field in inputs and outputs (sources and destinations) uint64_t amount_of_burned_assets = 0; for (auto& item : ftp.sources) { @@ -2291,10 +2305,19 @@ namespace currency amount_of_burned_assets -= item.amount; } } - ado.descriptor.current_supply -= amount_of_burned_assets; - + if (ado.version < ASSET_DESCRIPTOR_BASE_HF5_VER) + { + CHECK_AND_ASSERT_THROW_MES(ado.opt_descriptor.has_value(), "Internal error: opt_descriptor unset during ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN for version less then 2"); + ado.opt_descriptor->current_supply -= amount_of_burned_assets; + } + else + { + CHECK_AND_ASSERT_THROW_MES(!ado.opt_descriptor.has_value(), "Internal error: opt_descriptor unset during ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN for version less then 2"); + ado.opt_amount = amount_of_burned_assets; // TODO: support hidden supply -- sowle + } + gen_context.ao_amount_commitment = amount_of_burned_assets * gen_context.ao_asset_id_pt + gen_context.ao_amount_blinding_mask * crypto::c_point_G; - ado.amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key(); + ado.opt_amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key(); if (ftp.pevents_dispatcher) ftp.pevents_dispatcher->RAISE_DEBUG_EVENT(wde_construct_tx_handle_asset_descriptor_operation_before_burn{ &ado }); @@ -2318,10 +2341,19 @@ namespace currency item.asset_id = gen_context.ao_asset_id; } } - ado.descriptor.current_supply += amount_of_emitted_asset; + if (ado.version < ASSET_DESCRIPTOR_BASE_HF5_VER) + { + CHECK_AND_ASSERT_THROW_MES(ado.opt_descriptor.has_value(), "Internal error: opt_descriptor unset during ASSET_DESCRIPTOR_OPERATION_EMIT for version less then 2"); + ado.opt_descriptor->current_supply += amount_of_emitted_asset; + } + else + { + CHECK_AND_ASSERT_THROW_MES(!ado.opt_descriptor.has_value(), "Internal error: opt_descriptor unset during ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN for version less then 2"); + ado.opt_amount = amount_of_emitted_asset; // TODO: support hidden supply -- sowle + } gen_context.ao_amount_commitment = amount_of_emitted_asset * gen_context.ao_asset_id_pt + gen_context.ao_amount_blinding_mask * crypto::c_point_G; - ado.amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key(); + ado.opt_amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key(); } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) @@ -2333,25 +2365,6 @@ namespace currency } if (ftp.pevents_dispatcher) ftp.pevents_dispatcher->RAISE_DEBUG_EVENT(wde_construct_tx_handle_asset_descriptor_operation_before_seal{ &ado }); - ftp.need_to_generate_ado_proof = true; - /* - //seal it with owners signature - crypto::signature sig = currency::null_sig; - crypto::hash h = get_signature_hash_for_asset_operation(ado); - if (ftp.pthirdparty_sign_handler) - { - bool r = ftp.pthirdparty_sign_handler->sign(h, ftp.ado_current_asset_owner, sig); - CHECK_AND_ASSERT_MES(r, false, "asset thirparty sign failed"); - } - else - { - crypto::public_key pub_k = currency::null_pkey; - crypto::secret_key_to_public_key(sender_account_keys.spend_secret_key, pub_k); - CHECK_AND_ASSERT_MES(ftp.ado_current_asset_owner == pub_k, false, "asset owner key not matched with provided private key for asset operation signing"); - crypto::generate_signature(h, pub_k, account_keys.spend_secret_key, sig); - } - ado.opt_proof = sig; - */ } return true; } @@ -2374,7 +2387,8 @@ namespace currency crypto::secret_key& one_time_tx_secret_key = result.one_time_key; result.ftp = ftp; - CHECK_AND_ASSERT_MES(destinations.size() <= CURRENCY_TX_MAX_ALLOWED_OUTS, false, "Too many outs (" << destinations.size() << ")! Tx can't be constructed."); + CHECK_AND_ASSERT_MES(sources.size() <= CURRENCY_TX_MAX_ALLOWED_INPUTS, false, "Too many inputs: " << sources.size() << ", max allowed: " << CURRENCY_TX_MAX_ALLOWED_INPUTS << ". Tx cannot be constructed."); + CHECK_AND_ASSERT_MES(destinations.size() <= CURRENCY_TX_MAX_ALLOWED_OUTS, false, "Too many outputs: " << destinations.size() << ", max allowed: " << CURRENCY_TX_MAX_ALLOWED_OUTS << ". Tx cannot be constructed."); bool append_mode = false; if (flags&TX_FLAG_SIGNATURE_MODE_SEPARATE && tx.vin.size()) @@ -2600,8 +2614,7 @@ namespace currency // ASSET oprations handling if (tx.version > TRANSACTION_VERSION_PRE_HF4) { - asset_descriptor_operation* pado = nullptr; - pado = get_type_in_variant_container(tx.extra); + asset_descriptor_operation* pado = get_type_in_variant_container(tx.extra); if (pado) { bool r = construct_tx_handle_ado(sender_account_keys, ftp, *pado, gen_context, gen_context.tx_key, shuffled_dsts); @@ -2619,7 +2632,6 @@ namespace currency // construct outputs uint64_t native_coins_output_sum = 0; size_t output_index = tx.vout.size(); // in case of append mode we need to start output indexing from the last one + 1 - uint64_t range_proof_start_index = 0; std::set existing_derivation_hints, new_derivation_hints; CHECK_AND_ASSERT_MES(copy_all_derivation_hints_from_tx_to_container(tx, existing_derivation_hints), false, "move_all_derivation_hints_from_tx_to_container failed"); for(size_t destination_index = 0; destination_index < shuffled_dsts.size(); ++destination_index, ++output_index) @@ -2714,7 +2726,8 @@ namespace currency // generate proofs and signatures // (any changes made below should only affect the signatures/proofs and should not impact the prefix hash calculation) // - crypto::hash tx_prefix_hash = get_transaction_prefix_hash(tx); + result.tx_id = get_transaction_prefix_hash(tx); + const crypto::hash &tx_prefix_hash = result.tx_id; // ring signatures (per-input proofs) r = false; @@ -2760,7 +2773,7 @@ namespace currency // range proofs currency::zc_outs_range_proof range_proofs{}; - r = generate_zc_outs_range_proof(tx_prefix_hash, range_proof_start_index, gen_context, tx.vout, range_proofs); + r = generate_zc_outs_range_proof(tx_prefix_hash, gen_context, tx.vout, range_proofs); CHECK_AND_ASSERT_MES(r, false, "Failed to generate zc_outs_range_proof()"); tx.proofs.emplace_back(std::move(range_proofs)); @@ -2770,35 +2783,29 @@ namespace currency CHECK_AND_ASSERT_MES(r, false, "generate_tx_balance_proof failed"); tx.proofs.emplace_back(std::move(balance_proof)); - // asset operation proof (if necessary) + // optional asset operation proofs: amount commitment proof (required for register, emit, public burn) if (gen_context.ao_asset_id != currency::null_pkey) { - // construct the asset operation proof - // TODO @#@# add support for hidden supply + // asset amount commitment g proof (TODO @#@# add support for hidden supply) crypto::signature aop_g_sig{}; crypto::generate_signature(tx_prefix_hash, crypto::point_t(gen_context.ao_amount_blinding_mask * crypto::c_point_G).to_public_key(), gen_context.ao_amount_blinding_mask, aop_g_sig); asset_operation_proof aop{}; aop.opt_amount_commitment_g_proof = aop_g_sig; tx.proofs.emplace_back(std::move(aop)); } - if(ftp.need_to_generate_ado_proof) - { - asset_operation_ownership_proof aoop = AUTO_VAL_INIT(aoop); - if (ftp.pthirdparty_sign_handler) + // optional asset operation proofs: ownership proof for standard (non-eth) owner (using generic Shnorr signature with the spend secret key) + const asset_descriptor_operation* pado = get_type_in_variant_container(tx.extra); + if (pado != nullptr) + { + if ((pado->operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || pado->operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) && (!ftp.ado_sign_thirdparty)) { - //ask third party to generate proof - r = ftp.pthirdparty_sign_handler->sign(tx_prefix_hash, ftp.ado_current_asset_owner, aoop.gss); - CHECK_AND_ASSERT_MES(r, false, "Failed to sign ado by thirdparty"); - } - else - { - //generate signature by wallet account - r = crypto::generate_schnorr_sig(tx_prefix_hash, ftp.ado_current_asset_owner, sender_account_keys.spend_secret_key, aoop.gss); + asset_operation_ownership_proof aoop{}; + r = crypto::generate_schnorr_sig(tx_prefix_hash, sender_account_keys.spend_secret_key, aoop.gss); CHECK_AND_ASSERT_MES(r, false, "Failed to sign ado proof"); + if (ftp.pevents_dispatcher) ftp.pevents_dispatcher->RAISE_DEBUG_EVENT(wde_construct_tx_after_asset_ownership_proof_generated{ &aoop }); + tx.proofs.emplace_back(aoop); } - if (ftp.pevents_dispatcher) ftp.pevents_dispatcher->RAISE_DEBUG_EVENT(wde_construct_tx_after_asset_ownership_proof_generated{ &aoop }); - tx.proofs.emplace_back(aoop); } } @@ -2850,7 +2857,7 @@ namespace currency return reward; } //--------------------------------------------------------------- - std::string get_word_from_timstamp(uint64_t timestamp, bool use_password) + std::string get_word_from_timestamp(uint64_t timestamp, bool use_password) { uint64_t date_offset = timestamp > WALLET_BRAIN_DATE_OFFSET ? timestamp - WALLET_BRAIN_DATE_OFFSET : 0; uint64_t weeks_count = date_offset / WALLET_BRAIN_DATE_QUANTUM; @@ -2865,7 +2872,7 @@ namespace currency return tools::mnemonic_encoding::word_by_num(weeks_count_32); } //--------------------------------------------------------------- - uint64_t get_timstamp_from_word(std::string word, bool& password_used) + uint64_t get_timestamp_from_word(std::string word, bool& password_used) { uint64_t count_of_weeks = tools::mnemonic_encoding::num_by_word(word); if (count_of_weeks >= WALLET_BRAIN_DATE_MAX_WEEKS_COUNT) @@ -3625,13 +3632,13 @@ namespace currency return true; } //------------------------------------------------------------------ + #define PASSWORD_REGEXP R"([A-Za-z0-9~!?@#$%^&*_+|{}\[\]()<>:;"'\-=/.,]{0,40})" bool validate_password(const std::string& password) { - static const std::string allowed_password_symbols = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~!?@#$%^&*_+|{}[]()<>:;\"'-=\\/.,"; - size_t n = password.find_first_not_of(allowed_password_symbols, 0); - return n == std::string::npos; + // OLD: static const std::string allowed_password_symbols = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~!?@#$%^&*_+|{}[]()<>:;\"'-=\\/.,"; + static std::regex password_regexp(PASSWORD_REGEXP); + return std::regex_match(password, password_regexp); } - //------------------------------------------------------------------ #define ANTI_OVERFLOW_AMOUNT 1000000 #define GET_PERECENTS_BIG_NUMBERS(per, total) (per/ANTI_OVERFLOW_AMOUNT)*100 / (total/ANTI_OVERFLOW_AMOUNT) @@ -4458,6 +4465,45 @@ namespace currency } } //------------------------------------------------------------------ +#define ASSET_TICKER_REGEXP R"([A-Za-z0-9]{1,14})" +#define ASSET_FULL_NAME_REGEXP R"([A-Za-z0-9.,:!?\-() ]{0,400})" + bool validate_asset_ticker(const std::string& ticker) + { + static std::regex asset_ticker_regexp(ASSET_TICKER_REGEXP); + return std::regex_match(ticker, asset_ticker_regexp); + } + //------------------------------------------------------------------ + bool validate_asset_full_name(const std::string& full_name) + { + static std::regex asset_full_name_regexp(ASSET_FULL_NAME_REGEXP); + return std::regex_match(full_name, asset_full_name_regexp); + } + //------------------------------------------------------------------ + bool validate_asset_ticker_and_full_name(const asset_descriptor_base& adb) + { + if (!validate_asset_ticker(adb.ticker)) + return false; + + if (!validate_asset_full_name(adb.full_name)) + return false; + + //CHECK_AND_ASSERT_MES(validate_asset_ticker(adb.ticker), false, "asset's ticker isn't valid: " << adb.ticker); + //CHECK_AND_ASSERT_MES(validate_asset_full_name(adb.full_name), false, "asset's full_name isn't valid: " << adb.full_name); + return true; + } + //------------------------------------------------------------------ + void replace_asset_ticker_and_full_name_if_invalid(asset_descriptor_base& adb, const crypto::public_key& asset_id) + { + if (!validate_asset_ticker(adb.ticker)) + adb.ticker = "#BADASSET#"; + + if (!validate_asset_full_name(adb.full_name)) + { + std::string abcd = crypto::pod_to_hex(asset_id).substr(60, 4); // last 4 hex chars + adb.full_name = "#bad asset name " + abcd + "#"; + } + } + //------------------------------------------------------------------ std::string dump_ring_sig_data(const crypto::hash& hash_for_sig, const crypto::key_image& k_image, const std::vector& output_keys_ptrs, const std::vector& sig) { std::stringstream s; diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index 3e875c08..399a6d90 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -29,6 +29,7 @@ #include "currency_format_utils_transactions.h" #include "core_runtime_config.h" #include "wallet/wallet_public_structs_defs.h" +#include "wallet/wallet_public_structs_defs.h" #include "bc_attachments_helpers.h" #include "bc_payments_id_service.h" #include "bc_offers_service_basic.h" @@ -139,11 +140,13 @@ namespace currency bool hltc_our_out_is_before_expiration; }; - struct thirdparty_sign_handler + struct asset_eth_signer_i { - virtual bool sign(const crypto::hash& h, const crypto::public_key& owner_public_key, crypto::generic_schnorr_sig& sig); + virtual bool sign(const crypto::hash& h, const crypto::eth_public_key& asset_owner, crypto::eth_signature& sig) = 0; }; + typedef boost::variant asset_owner_key_v; + struct finalize_tx_param { uint64_t unlock_time; @@ -162,13 +165,11 @@ namespace currency uint64_t tx_version; uint64_t mode_separate_fee = 0; - epee::misc_utils::events_dispatcher* pevents_dispatcher; + epee::misc_utils::events_dispatcher* pevents_dispatcher = nullptr; tx_generation_context gen_context{}; // solely for consolidated txs + - //crypto::secret_key asset_control_key = currency::null_skey; - crypto::public_key ado_current_asset_owner = null_pkey; - thirdparty_sign_handler* pthirdparty_sign_handler = nullptr; - mutable bool need_to_generate_ado_proof = false; + bool ado_sign_thirdparty = false;//@#@ TODO: add to serialization map @zoidberg BEGIN_SERIALIZE_OBJECT() @@ -191,14 +192,13 @@ namespace currency { FIELD(gen_context); } - FIELD(ado_current_asset_owner) - FIELD(need_to_generate_ado_proof) END_SERIALIZE() }; struct finalized_tx { currency::transaction tx; + crypto::hash tx_id; crypto::secret_key one_time_key; finalize_tx_param ftp; std::string htlc_origin; @@ -208,6 +208,7 @@ namespace currency BEGIN_SERIALIZE_OBJECT() FIELD(tx) + FIELD(tx_id) FIELD(one_time_key) FIELD(ftp) FIELD(htlc_origin) @@ -261,6 +262,7 @@ namespace currency const transaction& tx; const crypto::hash& tx_id; const asset_descriptor_operation& ado; + uint64_t height = UINT64_MAX; // default value means the height of the upcoming block (top_block + 1) crypto::public_key asset_id = currency::null_pkey; crypto::point_t asset_id_pt = crypto::c_point_0; uint64_t amount_to_validate = 0; @@ -271,13 +273,18 @@ namespace currency bool generate_asset_surjection_proof(const crypto::hash& context_hash, bool has_non_zc_inputs, tx_generation_context& ogc, zc_asset_surjection_proof& result); bool verify_asset_surjection_proof(const transaction& tx, const crypto::hash& tx_id); bool generate_tx_balance_proof(const transaction &tx, const crypto::hash& tx_id, const tx_generation_context& ogc, uint64_t block_reward_for_miner_tx, zc_balance_proof& proof); - bool generate_zc_outs_range_proof(const crypto::hash& context_hash, size_t out_index_start, const tx_generation_context& outs_gen_context, + bool generate_zc_outs_range_proof(const crypto::hash& context_hash, const tx_generation_context& outs_gen_context, const std::vector& vouts, zc_outs_range_proof& result); bool check_tx_bare_balance(const transaction& tx, uint64_t additional_inputs_amount_and_fees_for_mining_tx = 0); bool check_tx_balance(const transaction& tx, const crypto::hash& tx_id, uint64_t additional_inputs_amount_and_fees_for_mining_tx = 0); bool validate_asset_operation_amount_commitment(asset_op_verification_context& context); const char* get_asset_operation_type_string(size_t asset_operation_type, bool short_name = false); + bool validate_asset_ticker(const std::string& ticker); + bool validate_asset_full_name(const std::string& full_name); + bool validate_asset_ticker_and_full_name(const asset_descriptor_base& adb); + void replace_asset_ticker_and_full_name_if_invalid(asset_descriptor_base& adb, const crypto::public_key& asset_id); + //--------------------------------------------------------------- bool construct_miner_tx(size_t height, size_t median_size, const boost::multiprecision::uint128_t& already_generated_coins, size_t current_block_size, @@ -457,9 +464,9 @@ namespace currency bool fill_block_rpc_details(block_rpc_extended_info& pei_rpc, const block_extended_info& bei_chain, const crypto::hash& h); void append_per_block_increments_for_tx(const transaction& tx, std::unordered_map& gindices); - std::string get_word_from_timstamp(uint64_t timestamp, bool use_password); - uint64_t get_timstamp_from_word(std::string word, bool& password_used, const std::string& buff); - uint64_t get_timstamp_from_word(std::string word, bool& password_used); + std::string get_word_from_timestamp(uint64_t timestamp, bool use_password); + uint64_t get_timestamp_from_word(std::string word, bool& password_used, const std::string& buff); + uint64_t get_timestamp_from_word(std::string word, bool& password_used); bool parse_vote(const std::string& buff, std::list>& votes); std::string generate_origin_for_htlc(const txout_htlc& htlc, const account_keys& acc_keys); @@ -546,7 +553,7 @@ namespace currency { assets_list.push_back(currency::asset_descriptor_with_id()); assets_list.back().asset_id = pr.first; - epee::misc_utils::cast_assign_a_to_b(assets_list.back(), static_cast(pr.second)); + epee::misc_utils::cast_assign_a_to_b(static_cast(pr.second), assets_list.back()); //*static_cast(&assets_list.back()) = pr.second; } } @@ -947,7 +954,15 @@ namespace currency } } } + /* //--------------------------------------------------------------- + template + typename std::enable_if_t, std::ostream&> operator<<(std::ostream& o, invocable_t callee) + { + callee(o); + return o; + } + */ //--------------------------------------------------------------- std::ostream& operator <<(std::ostream& o, const ref_by_id& r); std::ostream& operator <<(std::ostream& o, const std::type_info& ti); @@ -1205,7 +1220,13 @@ namespace currency tv.short_view = std::string("op:") + get_asset_operation_type_string(ado.operation_type, true); if (ado.opt_asset_id.has_value()) tv.short_view += std::string(" , id:") + crypto::pod_to_hex(ado.opt_asset_id); - tv.details_view = tv.short_view + std::string(" , ticker:") + ado.descriptor.ticker + std::string(" , cur.supply:") + print_money_brief(ado.descriptor.current_supply, ado.descriptor.decimal_point); + tv.details_view = tv.short_view; + if (ado.opt_descriptor.has_value()) + { + tv.details_view += std::string(" , ticker:") + ado.opt_descriptor->ticker + std::string(" , cur.supply:") + print_money_brief(ado.opt_descriptor->current_supply, ado.opt_descriptor->decimal_point); + } + //@#@ TODO: add other info from asset_descriptor_operation v2+ + return true; } template diff --git a/src/currency_core/miner.cpp b/src/currency_core/miner.cpp index d52e51e2..109d9966 100644 --- a/src/currency_core/miner.cpp +++ b/src/currency_core/miner.cpp @@ -40,24 +40,25 @@ namespace currency } - miner::miner(i_miner_handler* phandler, blockchain_storage& bc):m_stop(1), - //m_bc(bc), - m_template(boost::value_initialized()), - m_template_no(0), - m_diffic(0), - m_thread_index(0), - m_phandler(phandler), - m_height(0), - m_pausers_count(0), - m_threads_total(0), - m_starter_nonce(0), - m_do_print_hashrate(false), - m_do_mining(false), - m_current_hash_rate(0), - m_last_hr_merge_time(0), - m_hashes(0), - m_config(AUTO_VAL_INIT(m_config)), - m_mine_address{} + miner::miner(i_miner_handler* phandler, blockchain_storage& bc) + : m_stop(1) + , m_template(boost::value_initialized()) + , m_template_no(0) + , m_diffic(0) + , m_thread_index(0) + , m_phandler(phandler) + , m_height(0) + , m_pausers_count(0) + , m_block_template_ready(false) + , m_threads_total(0) + , m_starter_nonce(0) + , m_do_print_hashrate(false) + , m_do_mining(false) + , m_current_hash_rate(0) + , m_last_hr_merge_time(0) + , m_hashes(0) + , m_config(AUTO_VAL_INIT(m_config)) + , m_mine_address{} { } //----------------------------------------------------------------------------------------------------- @@ -76,6 +77,7 @@ namespace currency m_height = height; ++m_template_no; m_starter_nonce = crypto::rand(); + m_block_template_ready = true; return true; } //----------------------------------------------------------------------------------------------------- @@ -100,7 +102,8 @@ namespace currency } if(!m_phandler->get_block_template(bl, m_mine_address, m_mine_address, di, height, extra_nonce)) { - LOG_ERROR("Failed to get_block_template()"); + // it's quite possible that block template cannot be created at particular time; need to wait + m_block_template_ready = false; return false; } set_block_template(bl, di, height); @@ -110,7 +113,8 @@ namespace currency bool miner::on_idle() { m_update_block_template_interval.do_call([&](){ - if(is_mining())request_block_template(); + if(is_mining()) + request_block_template(); return true; }); @@ -233,8 +237,13 @@ namespace currency return false; } - if(!m_template_no) - request_block_template();//lets update block template + while(!m_template_no) + { + if (request_block_template()) + break; + std::this_thread::sleep_for(std::chrono::seconds(2)); + LOG_PRINT_L1("Trying to get block template...."); + } boost::interprocess::ipcdetail::atomic_write32(&m_stop, 0); boost::interprocess::ipcdetail::atomic_write32(&m_thread_index, 0); @@ -313,12 +322,11 @@ namespace currency uint64_t local_height = 0; crypto::hash local_blob_data_hash = null_hash; - //uint64_t local_template_height = 0; - block b; + block b{}; while(!m_stop) { - if(m_pausers_count)//anti split workaround + if(m_pausers_count != 0 || !m_block_template_ready) { misc_utils::sleep_no_w(100); continue; diff --git a/src/currency_core/miner.h b/src/currency_core/miner.h index 94caea2b..c8a37225 100644 --- a/src/currency_core/miner.h +++ b/src/currency_core/miner.h @@ -101,6 +101,7 @@ namespace currency volatile uint32_t m_thread_index; volatile uint32_t m_threads_total; std::atomic m_pausers_count; + std::atomic m_block_template_ready; epee::critical_section m_miners_count_lock; std::list m_threads; diff --git a/src/currency_core/tx_pool.cpp b/src/currency_core/tx_pool.cpp index 33315870..d076d147 100644 --- a/src/currency_core/tx_pool.cpp +++ b/src/currency_core/tx_pool.cpp @@ -247,7 +247,7 @@ namespace currency r = process_type_in_variant_container_and_make_sure_its_unique(tx.extra, [&](const asset_descriptor_operation& ado){ asset_op_verification_context avc = { tx, id, ado }; - return m_blockchain.validate_asset_operation_against_current_blochain_state(avc); + return m_blockchain.validate_asset_operation(avc, m_blockchain.get_current_blockchain_size()); }, true); CHECK_AND_ASSERT_MES_CUSTOM(r, false, { tvc.m_verification_failed = true; }, "post-HF4 tx: asset operation is invalid"); } diff --git a/src/currency_protocol/currency_protocol_defs.h b/src/currency_protocol/currency_protocol_defs.h index 2a0eead6..b1ee93bc 100644 --- a/src/currency_protocol/currency_protocol_defs.h +++ b/src/currency_protocol/currency_protocol_defs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018 Zano Project +// Copyright (c) 2014-2024 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 @@ -135,7 +135,7 @@ namespace currency struct CORE_SYNC_DATA { - uint64_t current_height; + uint64_t current_height; // height of the top block + 1 crypto::hash top_id; uint64_t last_checkpoint_height; uint64_t core_time; diff --git a/src/currency_protocol/currency_protocol_handler.h b/src/currency_protocol/currency_protocol_handler.h index 1e420919..c21ac71a 100644 --- a/src/currency_protocol/currency_protocol_handler.h +++ b/src/currency_protocol/currency_protocol_handler.h @@ -74,6 +74,10 @@ namespace currency //----------------------------------------------------------------------------------- void set_to_debug_mode(uint32_t ip); + bool is_remote_client_version_allowed(int build_number, size_t min_allowed_build_number = SIZE_MAX) const; + bool is_remote_client_version_allowed(const std::string& client_version) const; + void check_all_client_versions_are_okay(); + private: //----------------- commands handlers ---------------------------------------------- int handle_notify_new_block(int command, NOTIFY_NEW_BLOCK::request& arg, currency_connection_context& context); @@ -86,11 +90,12 @@ namespace currency - //----------------- i_bc_protocol_layout --------------------------------------- - virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, currency_connection_context& exclude_context); - virtual bool relay_transactions(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request& arg, currency_connection_context& exclude_context); + //----------------- i_currency_protocol --------------------------------------- + virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, currency_connection_context& exclude_context) override; + virtual bool relay_transactions(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request& arg, currency_connection_context& exclude_context) override; + virtual void on_hardfork_activated(size_t hardfork_id) override; //---------------------------------------------------------------------------------- - //bool get_payload_sync_data(HANDSHAKE_DATA::request& hshd, currency_connection_context& context); + bool request_missing_objects(currency_connection_context& context, bool check_having_blocks); bool on_connection_synchronized(); void relay_que_worker(); diff --git a/src/currency_protocol/currency_protocol_handler.inl b/src/currency_protocol/currency_protocol_handler.inl index 9f4c22de..b9c7a524 100644 --- a/src/currency_protocol/currency_protocol_handler.inl +++ b/src/currency_protocol/currency_protocol_handler.inl @@ -1,12 +1,14 @@ -// Copyright (c) 2014-2018 Zano Project +// Copyright (c) 2014-2024 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 // file COPYING or http://www.opensource.org/licenses/mit-license.php. - #include +#include "currency_protocol_handler.h" #include "currency_core/currency_format_utils.h" #include "profile_tools.h" +#include + namespace currency { @@ -95,7 +97,7 @@ namespace currency ss << std::setw(29) << std::left << "Remote Host" << std::setw(20) << "Peer id" - << std::setw(25) << "Recv/Sent (idle,sec)" + << std::setw(27) << "Recv/Sent (idle,sec)" << std::setw(25) << "State" << std::setw(20) << "Livetime" << std::setw(20) << "Client version" << ENDL; @@ -109,7 +111,7 @@ namespace currency conn_ss << std::setw(29) << std::left << std::string(cntxt.m_is_income ? "[INC]":"[OUT]") + epst::get_ip_string_from_int32(cntxt.m_remote_ip) + ":" + std::to_string(cntxt.m_remote_port) << std::setw(20) << std::hex << peer_id - << std::setw(25) << std::to_string(cntxt.m_recv_cnt)+ "(" + std::to_string(time(NULL) - cntxt.m_last_recv) + ")" + "/" + std::to_string(cntxt.m_send_cnt) + "(" + std::to_string(time(NULL) - cntxt.m_last_send) + ")" + << std::setw(27) << std::to_string(cntxt.m_recv_cnt)+ "(" + std::to_string(time(NULL) - cntxt.m_last_recv) + ")" + "/" + std::to_string(cntxt.m_send_cnt) + "(" + std::to_string(time(NULL) - cntxt.m_last_send) + ")" << std::setw(25) << get_protocol_state_string(cntxt.m_state) << std::setw(20) << epee::misc_utils::get_time_interval_string(livetime) << std::setw(20) << cntxt.m_remote_version @@ -128,9 +130,12 @@ namespace currency template bool t_currency_protocol_handler::process_payload_sync_data(const CORE_SYNC_DATA& hshd, currency_connection_context& context, bool is_inital) { - - context.m_remote_version = hshd.client_version; + if (!tools::parse_client_version_build_number(context.m_remote_version, context.m_build_number)) + { + LOG_PRINT_RED_L0("Couldn't parse remote node's version: " << context.m_remote_version << ". Connection will be dropped."); + return false; + } if(context.m_state == currency_connection_context::state_befor_handshake && !is_inital) return true; @@ -988,7 +993,7 @@ namespace currency //------------------------------------------------------------------------------------------------------------------------ template bool t_currency_protocol_handler::relay_transactions(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request& arg, currency_connection_context& exclude_context) - { + { #ifdef ASYNC_RELAY_MODE { CRITICAL_REGION_LOCAL(m_relay_que_lock); @@ -1002,4 +1007,52 @@ namespace currency return relay_post_notify(arg, exclude_context); #endif } -} + //------------------------------------------------------------------------------------------------------------------------ + template + void t_currency_protocol_handler::on_hardfork_activated(size_t hardfork_id) + { + check_all_client_versions_are_okay(); + } + //------------------------------------------------------------------------------------------------------------------------ + template + bool t_currency_protocol_handler::is_remote_client_version_allowed(int build_number, size_t min_allowed_build_number /*= SIZE_MAX*/) const + { + if (min_allowed_build_number == SIZE_MAX) + min_allowed_build_number = m_core.get_blockchain_storage().get_core_runtime_config().get_min_allowed_build_version_for_height(m_core.get_top_block_height() + 1); + + if (build_number < static_cast(min_allowed_build_number)) + return false; + + return true; + } + //------------------------------------------------------------------------------------------------------------------------ + template + bool t_currency_protocol_handler::is_remote_client_version_allowed(const std::string& client_version) const + { + int major = -1, minor = -1, revision = -1, build_number = -1; + std::string commit_id; + bool dirty = false; + if (!tools::parse_client_version(client_version, major, minor, revision, build_number, commit_id, dirty)) + return false; + + return is_remote_client_version_allowed(build_number); + } + //------------------------------------------------------------------------------------------------------------------------ + template + void t_currency_protocol_handler::check_all_client_versions_are_okay() + { + size_t min_allowed_build_number = m_core.get_blockchain_storage().get_core_runtime_config().get_min_allowed_build_version_for_height(m_core.get_top_block_height() + 1); + + m_p2p->for_each_connection([&](const connection_context& cc, nodetool::peerid_type peer_id) + { + if (!is_remote_client_version_allowed(cc.m_build_number, min_allowed_build_number)) + { + LOG_PRINT_CC_YELLOW(cc, "client's build number is " << cc.m_build_number << ", which is absolutely not okay in the current hardfork era, prompting us to adjust our connections accordingly.", LOG_LEVEL_0); + m_p2p->drop_connection(cc); + } + return true; // = continue + }); + } + + +} // namespace currency diff --git a/src/currency_protocol/currency_protocol_handler_common.h b/src/currency_protocol/currency_protocol_handler_common.h index d07153f5..ba640896 100644 --- a/src/currency_protocol/currency_protocol_handler_common.h +++ b/src/currency_protocol/currency_protocol_handler_common.h @@ -18,6 +18,7 @@ namespace currency { virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, currency_connection_context& exclude_context)=0; virtual bool relay_transactions(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request& arg, currency_connection_context& exclude_context)=0; + virtual void on_hardfork_activated(size_t hardfork_id) {} //virtual bool request_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, currency_connection_context& context)=0; }; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 5f46b088..f5f768fc 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -61,7 +61,7 @@ struct core_critical_error_handler_t : public currency::i_critical_error_handler if (dont_stop_on_time_error) return false; // ignore such errors - LOG_ERROR(ENDL << ENDL << "Serious time sync problem detected, daemon will stop immediately" << ENDL << ENDL); + LOG_ERROR(ENDL << ENDL << "Serious TIME sync problem detected, daemon will stop immediately" << ENDL << ENDL); // stop handling dch.stop_handling(); diff --git a/src/daemon/daemon_commands_handler.h b/src/daemon/daemon_commands_handler.h index c960a39c..1b26fb73 100644 --- a/src/daemon/daemon_commands_handler.h +++ b/src/daemon/daemon_commands_handler.h @@ -48,6 +48,7 @@ public: m_cmd_binder.set_handler("print_tx_prun_info", boost::bind(&daemon_commands_handler::print_tx_prun_info, this, ph::_1), "Print tx prunning info"); m_cmd_binder.set_handler("print_tx", boost::bind(&daemon_commands_handler::print_tx, this, ph::_1), "Print transaction, print_tx "); m_cmd_binder.set_handler("print_asset_info", boost::bind(&daemon_commands_handler::print_asset_info, this, ph::_1), "Print information about the given asset by its id"); + m_cmd_binder.set_handler("print_blocked_ips", boost::bind(&daemon_commands_handler::print_blocked_ips, this, ph::_1), "Print ip address blacklists"); m_cmd_binder.set_handler("start_mining", boost::bind(&daemon_commands_handler::start_mining, this, ph::_1), "Start mining for specified address, start_mining [threads=1]"); m_cmd_binder.set_handler("stop_mining", boost::bind(&daemon_commands_handler::stop_mining, this, ph::_1), "Stop mining"); m_cmd_binder.set_handler("print_pool", boost::bind(&daemon_commands_handler::print_pool, this, ph::_1), "Print transaction pool (long format)"); @@ -235,6 +236,20 @@ private: return true; } //-------------------------------------------------------------------------------- + bool print_blocked_ips(const std::vector& args) + { + std::map blocklist; + m_srv.get_ip_block_list(blocklist); + std::stringstream ss; + ss << "BLOCKED IPS:" << ENDL; + for (const auto& e : blocklist) + { + ss << string_tools::get_ip_string_from_int32(e.first) << ", time: " << std::put_time(std::localtime(&e.second), "%Y-%m-%d %H:%M:%S") << ENDL; + } + LOG_PRINT_L0(ss.str()); + return true; + } + //-------------------------------------------------------------------------------- bool print_bc(const std::vector& args) { if (!args.size()) @@ -806,17 +821,21 @@ private: size_t idx = 0; for(auto& aop: asset_history) { - ss << "[" << std::setw(2) << idx << "] operation: " << currency::get_asset_operation_type_string(aop.operation_type) << " (" << (int)aop.operation_type << ")" << ENDL << - " ticker: \"" << aop.descriptor.ticker << "\"" << ENDL << - " full name: \"" << aop.descriptor.full_name << "\"" << ENDL << - " meta info: \"" << aop.descriptor.meta_info << "\"" << ENDL << - " current supply: " << currency::print_money_brief(aop.descriptor.current_supply, aop.descriptor.decimal_point) << ENDL << - " max supply: " << currency::print_money_brief(aop.descriptor.total_max_supply, aop.descriptor.decimal_point) << ENDL << - " decimal point: " << (int)aop.descriptor.decimal_point << ENDL << - " owner * 1/8: " << aop.descriptor.owner << ENDL << - " amount cmt * 1/8: " << aop.amount_commitment << ENDL << - " hidden supply: " << (aop.descriptor.hidden_supply ? "yes" : "no") << ENDL << - ""; + if (aop.opt_descriptor.has_value()) + { + const currency::asset_descriptor_base& adb = aop.opt_descriptor.value(); + ss << "[" << std::setw(2) << idx << "] operation: " << currency::get_asset_operation_type_string(aop.operation_type) << " (" << (int)aop.operation_type << ")" << ENDL << + " ticker: \"" << adb.ticker << "\"" << ENDL << + " full name: \"" << adb.full_name << "\"" << ENDL << + " meta info: \"" << adb.meta_info << "\"" << ENDL << + " current supply: " << currency::print_money_brief(adb.current_supply, adb.decimal_point) << ENDL << + " max supply: " << currency::print_money_brief(adb.total_max_supply, adb.decimal_point) << ENDL << + " decimal point: " << (int)adb.decimal_point << ENDL << + " owner * 1/8: " << adb.owner << ENDL << + " amount cmt * 1/8: " << (aop.opt_amount_commitment.has_value() ? aop.opt_amount_commitment.value() : currency::null_pkey) << ENDL << + " hidden supply: " << (adb.hidden_supply ? "yes" : "no") << ENDL << + ""; + } ++idx; } diff --git a/src/gui/qt-daemon/application/mainwindow.cpp b/src/gui/qt-daemon/application/mainwindow.cpp index 90f3b422..950c93af 100644 --- a/src/gui/qt-daemon/application/mainwindow.cpp +++ b/src/gui/qt-daemon/application/mainwindow.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018 Zano Project +// Copyright (c) 2014-2024 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Boolberry developers // Distributed under the MIT/X11 software license, see the accompanying @@ -714,9 +714,12 @@ bool MainWindow::show_inital() { TRY_ENTRY(); if (load_app_config()) + { restore_pos(true); + } else { + LOG_PRINT_L1("set defaults values to config"); m_config = AUTO_VAL_INIT(m_config); this->show(); QSize sz = AUTO_VAL_INIT(sz); @@ -976,6 +979,11 @@ QString MainWindow::start_backend(const QString& params) CATCH_ENTRY_FAIL_API_RESPONCE(); } +void MainWindow::show_notification(const QString& title, const QString& message) +{ + show_notification(title.toStdString(), message.toStdString()); +} + QString MainWindow::sync_call(const QString& func_name, const QString& params) { if (func_name == "test_call") @@ -1107,6 +1115,7 @@ bool MainWindow::get_is_disabled_notifications(const QString& param) } bool MainWindow::set_is_disabled_notifications(const bool& param) { + LOG_PRINT_L1("set_is_disabled_notifications: notifications were " << (m_config.disable_notifications ? "DISABLED" : "ENABLED") << " -> now " << (param ? "DISABLED" : "ENABLED")); m_config.disable_notifications = param; return m_config.disable_notifications; } @@ -1712,7 +1721,7 @@ QString MainWindow::have_secure_app_data(const QString& param) CATCH_ENTRY_FAIL_API_RESPONCE(); } -QString MainWindow::drop_secure_app_data(const QString& param) +QString MainWindow::drop_secure_app_data() { TRY_ENTRY(); LOG_API_TIMING(); @@ -2468,6 +2477,10 @@ QString MainWindow::print_log(const QString& param) void MainWindow::show_notification(const std::string& title, const std::string& message) { TRY_ENTRY(); + + if (m_config.disable_notifications) + return; + LOG_PRINT_L1("system notification: \"" << title << "\", \"" << message << "\""); // it's expected that title and message are utf-8 encoded! @@ -2482,5 +2495,3 @@ void MainWindow::show_notification(const std::string& title, const std::string& #endif CATCH_ENTRY2(void()); } - - diff --git a/src/gui/qt-daemon/application/mainwindow.h b/src/gui/qt-daemon/application/mainwindow.h index 0f575b6b..30dc1b6c 100644 --- a/src/gui/qt-daemon/application/mainwindow.h +++ b/src/gui/qt-daemon/application/mainwindow.h @@ -112,7 +112,7 @@ public: QString get_network_type(const QString& param); QString transfer(const QString& param); QString have_secure_app_data(const QString& param); - QString drop_secure_app_data(const QString& param); + QString drop_secure_app_data(); QString get_secure_app_data(const QString& param); QString store_secure_app_data(const QString& param, const QString& password); QString set_master_password(const QString& param); @@ -190,6 +190,7 @@ public: void on_menu_show(const QString& param); QString is_remnotenode_mode_preconfigured(const QString& param); QString start_backend(const QString& params); + void show_notification(const QString& title, const QString& message); QString async_call(const QString& func_name, const QString& params); QString sync_call(const QString& func_name, const QString& params); diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 2fb143cc..7cd0e5e5 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 2fb143cc67280f0e0cfcd3165e1c087ba8279edf +Subproject commit 7cd0e5e54a0d692ea819b9653f60a1cd5512dc2b diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 9ec3cc17..38b5a5ab 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -121,6 +121,7 @@ namespace nodetool peerlist_manager& get_peerlist_manager(){return m_peerlist;} bool handle_maintainers_entry(const maintainers_entry& me); bool get_maintainers_info(maintainers_info_external& me); + void get_ip_block_list(std::map& blocklist); typedef COMMAND_REQUEST_STAT_INFO_T COMMAND_REQUEST_STAT_INFO; private: diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 46a39ee5..fb1c4109 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -448,6 +448,13 @@ namespace nodetool } //----------------------------------------------------------------------------------- template + void node_server::get_ip_block_list(std::map& blocklist) + { + CRITICAL_REGION_LOCAL(m_blocked_ips_lock); + blocklist = m_blocked_ips; + } + //----------------------------------------------------------------------------------- + template bool node_server::on_maintainers_entry_update() { LOG_PRINT_CHANNEL_COLOR2(NULL, NULL, "Fresh maintainers info recieved(timestamp: " << m_maintainers_info_local.timestamp << ")", LOG_LEVEL_0, epee::log_space::console_color_magenta); @@ -523,7 +530,7 @@ namespace nodetool return; } - if (!tools::check_remote_client_version(rsp.payload_data.client_version)) + if (!m_payload_handler.is_remote_client_version_allowed(rsp.payload_data.client_version)) { LOG_PRINT_CC_YELLOW(context, "COMMAND_HANDSHAKE Failed, wrong client version: " << rsp.payload_data.client_version << ", closing connection.", LOG_LEVEL_1); return; @@ -1391,7 +1398,7 @@ namespace nodetool return 1; } - if (!tools::check_remote_client_version(arg.payload_data.client_version)) + if (!m_payload_handler.is_remote_client_version_allowed(arg.payload_data.client_version)) { LOG_PRINT_CCONTEXT_L2("COMMAND_HANDSHAKE: wrong client version: " << arg.payload_data.client_version << ", closing connection."); drop_connection(context); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 371d0a81..88938641 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -75,7 +75,8 @@ namespace currency return true; } #define check_core_ready() check_core_ready_(LOCAL_FUNCTION_DEF__) -#define CHECK_CORE_READY() if(!check_core_ready()){res.status = API_RETURN_CODE_BUSY;return true;} +#define CHECK_CORE_READY() if (!check_core_ready()) {res.status = API_RETURN_CODE_BUSY; return true; } +#define CHECK_CORE_READY_WE() if (!check_core_ready()) {error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; error_resp.message = "Core is busy."; return false; } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_height(const COMMAND_RPC_GET_HEIGHT::request& req, COMMAND_RPC_GET_HEIGHT::response& res, connection_context& cntx) { @@ -575,7 +576,7 @@ namespace currency //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_pos_mining_details(const COMMAND_RPC_GET_POS_MINING_DETAILS::request& req, COMMAND_RPC_GET_POS_MINING_DETAILS::response& res, connection_context& cntx) { - if (!m_p2p.get_connections_count()) + if (!m_ignore_status && !m_p2p.get_connections_count()) { res.status = API_RETURN_CODE_DISCONNECTED; return true; @@ -750,6 +751,74 @@ namespace currency return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_decrypt_tx_details(const COMMAND_RPC_DECRYPT_TX_DETAILS::request& req, COMMAND_RPC_DECRYPT_TX_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx) + { +#define LOCAL_CHECK(cond, msg) if (!(cond)) { error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; error_resp.message = msg; LOG_PRINT_L1("on_decrypt_tx_details: " << error_resp.message); return false; } +#define LOCAL_CHECK_INT_ERR(cond, msg) if (!(cond)) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = msg; LOG_PRINT_L1("on_decrypt_tx_details: " << error_resp.message); return false; } + + LOCAL_CHECK(req.tx_id.empty() != req.tx_blob.empty(), "One of either tx_id or tx_blob must be specified."); + + transaction tx{}; + if (!req.tx_id.empty()) + { + CHECK_CORE_READY_WE(); + + crypto::hash tx_id{}; + LOCAL_CHECK(crypto::parse_tpod_from_hex_string(req.tx_id, tx_id), "tx_id is given, but it's invalid"); + LOCAL_CHECK(m_core.get_transaction(tx_id, tx), "tx with the given tx_id could be found in the blockchain"); + } + else + { + blobdata decoded_blob = string_encoding::base64_decode(req.tx_blob); + if (!t_unserializable_object_from_blob(tx, decoded_blob)) + { + // unable to decode tx_blob as base64, try once again as hex-encoding + decoded_blob.clear(); + string_tools::parse_hexstr_to_binbuff(req.tx_blob, decoded_blob); + LOCAL_CHECK(t_unserializable_object_from_blob(tx, decoded_blob), "tx_id is not given, and tx_blob is invalid"); + } + } + + crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + crypto::point_t R{}; + LOCAL_CHECK(tx_pub_key != null_pkey && R.from_public_key(tx_pub_key) && R.is_in_main_subgroup(), "unsigned_tx: tx public key is missing or invalid"); + + LOCAL_CHECK(tx_pub_key == (crypto::scalar_t(req.tx_secret_key) * crypto::c_point_G).to_public_key(), "tx_secret_key doesn't match the transaction public key"); + + LOCAL_CHECK(req.outputs_addresses.size() == tx.vout.size(), "outputs_addresses count (" + epee::string_tools::num_to_string_fast(req.outputs_addresses.size()) + " doesn't match tx.vout size (" + epee::string_tools::num_to_string_fast(tx.vout.size()) + ")"); + + for(size_t i = 0; i < req.outputs_addresses.size(); ++i) + { + if (req.outputs_addresses[i].empty()) + continue; // skip this output if the given address is empty string + + account_public_address addr{}; + payment_id_t payment_id{}; + LOCAL_CHECK(currency::get_account_address_and_payment_id_from_str(addr, payment_id, req.outputs_addresses[i]) && payment_id.empty(), "output address #" + epee::string_tools::num_to_string_fast(i) + " couldn't be parsed or it is an integrated address (which is not supported)"); + + tx_out_v& out_v = tx.vout[i]; + LOCAL_CHECK(out_v.type() == typeid(tx_out_zarcanum), "tx output #" + epee::string_tools::num_to_string_fast(i) + " has wrong type"); + const tx_out_zarcanum& zo = boost::get(out_v); + + crypto::key_derivation derivation{}; + LOCAL_CHECK_INT_ERR(crypto::generate_key_derivation(addr.view_public_key, req.tx_secret_key, derivation), "output #" + epee::string_tools::num_to_string_fast(i) + ": generate_key_derivation failed"); + + auto& decoded_out = res.decoded_outputs.emplace_back(); + decoded_out.out_index = i; + decoded_out.address = req.outputs_addresses[i]; + crypto::scalar_t amount_blinding_mask{}, asset_id_blinding_mask{}; + LOCAL_CHECK(currency::decode_output_amount_and_asset_id(zo, derivation, i, decoded_out.amount, decoded_out.asset_id, amount_blinding_mask, asset_id_blinding_mask), "output #" + epee::string_tools::num_to_string_fast(i) + ": cannot be decoded"); + } + + res.tx_in_json = currency::obj_to_json_str(tx); + res.verified_tx_id = get_transaction_hash(tx); + + res.status = API_RETURN_CODE_OK; + return true; +#undef LOCAL_CHECK +#undef LOCAL_CHECK_INT_ERR + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_main_block_details(const COMMAND_RPC_GET_BLOCK_DETAILS::request& req, COMMAND_RPC_GET_BLOCK_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx) { if (!m_core.get_blockchain_storage().get_main_block_rpc_details(req.id, res.block_details)) @@ -793,7 +862,7 @@ namespace currency return true; } - if (!m_p2p.get_payload_object().get_synchronized_connections_count()) + if (!m_ignore_status && !m_p2p.get_payload_object().get_synchronized_connections_count()) { LOG_PRINT_L0("[on_send_raw_tx]: Failed to send, daemon not connected to net"); res.status = API_RETURN_CODE_DISCONNECTED; @@ -1001,7 +1070,7 @@ namespace currency //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_submitblock(const COMMAND_RPC_SUBMITBLOCK::request& req, COMMAND_RPC_SUBMITBLOCK::response& res, epee::json_rpc::error& error_resp, connection_context& cntx) { - CHECK_CORE_READY(); + CHECK_CORE_READY_WE(); if(req.size()!=1) { error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; @@ -1044,8 +1113,7 @@ namespace currency //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_submitblock2(const COMMAND_RPC_SUBMITBLOCK2::request& req, COMMAND_RPC_SUBMITBLOCK2::response& res, epee::json_rpc::error& error_resp, connection_context& cntx) { - CHECK_CORE_READY(); - + CHECK_CORE_READY_WE(); block b = AUTO_VAL_INIT(b); if (!parse_and_validate_block_from_blob(req.b, b)) @@ -1301,6 +1369,110 @@ namespace currency return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_find_outs_in_recent_blocks(const COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS::request& req, COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS::response& resp, epee::json_rpc::error& error_resp, connection_context& cntx) + { +#define LOCAL_CHECK(cond, msg) if (!(cond)) { error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; error_resp.message = msg; LOG_PRINT_L1("on_find_outs_in_recent_blocks: " << msg); return false; } +#define LOCAL_CHECK_INT_ERR(cond, msg) if (!(cond)) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = msg; LOG_PRINT_L1("on_find_outs_in_recent_blocks: " << msg); return false; } + + LOCAL_CHECK(req.address != account_public_address{}, "address is missing"); + LOCAL_CHECK(req.viewkey != null_skey, "viewkey is missing"); + LOCAL_CHECK(req.blocks_limit <= 5, "blocks_limit is out of allowed bounds"); + + // verify addess keys + crypto::point_t view_pk, spend_pk; + LOCAL_CHECK(view_pk.from_public_key(req.address.view_public_key), "cannon load point from address.view_public_key"); + LOCAL_CHECK(view_pk.is_in_main_subgroup(), "address.view_public_key isn't in main subgroup"); + LOCAL_CHECK(spend_pk.from_public_key(req.address.spend_public_key), "cannon load point from address.spend_public_key"); + LOCAL_CHECK(spend_pk.is_in_main_subgroup(), "address.spend_public_key isn't in main subgroup"); + + // verify viewkey + crypto::scalar_t view_sc = req.viewkey; + LOCAL_CHECK(view_sc.is_reduced(), "viewkey is invalid"); + LOCAL_CHECK(view_sc * crypto::c_point_G == view_pk, "viewkey doesn't correspond to the given address"); + + const blockchain_storage& bcs = m_core.get_blockchain_storage(); + resp.blockchain_top_block_height = bcs.get_top_block_height(); + resp.blocks_limit = req.blocks_limit; + + // get blockchain transactions + std::unordered_map, std::list>> blockchain_txs; // block height -> (vector of tx_ids, list of txs) + if (req.blocks_limit > 0) + { + uint64_t start_offset = resp.blockchain_top_block_height - req.blocks_limit + 1; + std::list recent_blocks; + LOCAL_CHECK_INT_ERR(bcs.get_blocks(start_offset, static_cast(req.blocks_limit), recent_blocks), "cannot get recent blocks"); + + std::vector blockchain_tx_ids, missed_tx; + for(auto& b : recent_blocks) + { + blockchain_tx_ids.insert(blockchain_tx_ids.end(), b.tx_hashes.begin(), b.tx_hashes.end()); + uint64_t height = get_block_height(b); + auto& el = blockchain_txs[height]; + el.first = b.tx_hashes; + missed_tx.clear(); + LOCAL_CHECK_INT_ERR(bcs.get_transactions(b.tx_hashes, el.second, missed_tx), "bcs.get_transactions failed"); + LOCAL_CHECK_INT_ERR(missed_tx.empty(), "missed_tx is not empty"); + LOCAL_CHECK_INT_ERR(el.first.size() == el.second.size(), "el.first.size() != el.second.size()"); + } + } + + // get pool transactions + std::list pool_txs; + LOCAL_CHECK_INT_ERR(m_core.get_tx_pool().get_transactions(pool_txs), "cannot get txs from pool"); + + // processor lambda + auto process_tx = [&](const transaction& tx, const crypto::hash& tx_id, const int64_t height) { + crypto::key_derivation derivation{}; + LOCAL_CHECK(generate_key_derivation(get_tx_pub_key_from_extra(tx), req.viewkey, derivation), "generate_key_derivation failed"); + + for(size_t i = 0, sz = tx.vout.size(); i < sz; ++i) + { + const tx_out_v& outv = tx.vout[i]; + if (outv.type() != typeid(tx_out_zarcanum)) + continue; + + uint64_t decoded_amount = 0; + crypto::public_key decoded_asset_id{}; + crypto::scalar_t amount_blinding_mask{}, asset_id_blinding_mask{}; + if (!is_out_to_acc(req.address, boost::get(outv), derivation, i, decoded_amount, decoded_asset_id, amount_blinding_mask, asset_id_blinding_mask)) + continue; + + auto& el = resp.outputs.emplace_back(); + el.amount = decoded_amount; + el.asset_id = decoded_asset_id; + el.tx_id = tx_id; + el.tx_block_height = height; + el.output_tx_index = i; + } + return true; + }; + + // process blockchain txs + for(auto& [height, pair] : blockchain_txs) + { + LOCAL_CHECK(pair.first.size() == pair.second.size(), "container size inconsistency"); + auto tx_it = pair.second.begin(); + for(size_t i = 0, sz = pair.first.size(); i < sz; ++i, ++tx_it) + { + const crypto::hash& tx_id = pair.first[i]; + LOCAL_CHECK(process_tx(*tx_it, tx_id, height), "process blockchain tx failed for tx " + crypto::pod_to_hex(tx_id)); + } + } + + // process pool txs + for(auto& tx : pool_txs) + { + crypto::hash tx_id = get_transaction_hash(tx); + LOCAL_CHECK(process_tx(tx, tx_id, -1), "process pool tx failed for tx " + crypto::pod_to_hex(tx_id)); + } + + resp.status = resp.outputs.empty() ? API_RETURN_CODE_NOT_FOUND : API_RETURN_CODE_OK; + return true; + +#undef LOCAL_CHECK_INT_ERR +#undef LOCAL_CHECK + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_aliases_by_address(const COMMAND_RPC_GET_ALIASES_BY_ADDRESS::request& req, COMMAND_RPC_GET_ALIASES_BY_ADDRESS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx) { account_public_address addr = AUTO_VAL_INIT(addr); diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 02e7b478..e95f5a41 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018 Zano Project +// Copyright (c) 2014-2024 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 @@ -89,11 +89,13 @@ namespace currency bool on_get_votes(const COMMAND_RPC_GET_VOTES::request& req, COMMAND_RPC_GET_VOTES::response& res, connection_context& cntx); bool on_get_asset_info(const COMMAND_RPC_GET_ASSET_INFO::request& req, COMMAND_RPC_GET_ASSET_INFO::response& res, connection_context& cntx); bool on_get_assets_list(const COMMAND_RPC_GET_ASSETS_LIST::request& req, COMMAND_RPC_GET_ASSETS_LIST::response& res, connection_context& cntx); + bool on_decrypt_tx_details(const COMMAND_RPC_DECRYPT_TX_DETAILS::request& req, COMMAND_RPC_DECRYPT_TX_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); bool on_get_main_block_details(const COMMAND_RPC_GET_BLOCK_DETAILS::request& req, COMMAND_RPC_GET_BLOCK_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); bool on_get_alt_block_details(const COMMAND_RPC_GET_BLOCK_DETAILS::request& req, COMMAND_RPC_GET_BLOCK_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); bool on_get_alt_blocks_details(const COMMAND_RPC_GET_ALT_BLOCKS_DETAILS::request& req, COMMAND_RPC_GET_ALT_BLOCKS_DETAILS::response& res, connection_context& cntx); bool on_get_est_height_from_date(const COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE::request& req, COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE::response& res, connection_context& cntx); + bool on_find_outs_in_recent_blocks(const COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS::request& req, COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); bool on_validate_signature(const COMMAND_VALIDATE_SIGNATURE::request& req, COMMAND_VALIDATE_SIGNATURE::response& res, epee::json_rpc::error& er, connection_context& cntx); @@ -133,6 +135,8 @@ namespace currency MAP_JON_RPC_WE("get_alias_by_address", on_aliases_by_address, COMMAND_RPC_GET_ALIASES_BY_ADDRESS) MAP_JON_RPC_WE("get_alias_reward", on_get_alias_reward, COMMAND_RPC_GET_ALIAS_REWARD) MAP_JON_RPC ("get_est_height_from_date", on_get_est_height_from_date, COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE) + MAP_JON_RPC_WE("find_outs_in_recent_blocks", on_find_outs_in_recent_blocks, COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS) + //block explorer api MAP_JON_RPC ("get_blocks_details", on_rpc_get_blocks_details, COMMAND_RPC_GET_BLOCKS_DETAILS) MAP_JON_RPC_WE("get_tx_details", on_get_tx_details, COMMAND_RPC_GET_TX_DETAILS) @@ -150,9 +154,11 @@ namespace currency MAP_JON_RPC ("getrandom_outs1", on_get_random_outs1, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS) MAP_JON_RPC ("getrandom_outs3", on_get_random_outs3, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3) MAP_JON_RPC ("get_votes", on_get_votes, COMMAND_RPC_GET_VOTES) + //assets api MAP_JON_RPC ("get_asset_info", on_get_asset_info, COMMAND_RPC_GET_ASSET_INFO) MAP_JON_RPC ("get_assets_list", on_get_assets_list, COMMAND_RPC_GET_ASSETS_LIST) + MAP_JON_RPC_WE("decrypt_tx_details", on_decrypt_tx_details, COMMAND_RPC_DECRYPT_TX_DETAILS) MAP_JON_RPC_WE("get_main_block_details", on_get_main_block_details, COMMAND_RPC_GET_BLOCK_DETAILS) MAP_JON_RPC_WE("get_alt_block_details", on_get_alt_block_details, COMMAND_RPC_GET_BLOCK_DETAILS) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 9a89d043..b5d6143d 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -23,6 +23,7 @@ #include #include #include +#include //#include "currency_core/basic_api_response_codes.h" namespace currency @@ -178,6 +179,58 @@ namespace currency }; }; + + struct COMMAND_RPC_DECRYPT_TX_DETAILS + { + DOC_COMMAND("Decrypts transaction private information. Should be used only with your own local daemon for security reasons."); + + struct request + { + std::string tx_id; + currency::blobdata tx_blob; + crypto::secret_key tx_secret_key; + std::vector outputs_addresses; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_id) DOC_DSCR("[either] ID for a transaction if it is already in the blockchain. Can be ommited if tx_blob is provided.") DOC_EXMP("a6e8da986858e6825fce7a192097e6afae4e889cabe853a9c29b964985b23da8") DOC_END + KV_SERIALIZE(tx_blob) DOC_DSCR("[or] base64-encoded or hex-encoded tx blob. Can be ommited if tx_id is provided.") DOC_EXMP("ewogICJ2ZXJzaW9uIjogMSwgC....iAgInZpbiI6IFsgewogICAgIC") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(tx_secret_key) DOC_DSCR("Hex-encoded transaction secret key.") DOC_EXMP("2e0b840e70dba386effd64c5d988622dea8c064040566e6bf035034cbb54a5c08") DOC_END + KV_SERIALIZE(outputs_addresses) DOC_DSCR("Address of each of tx's output. Order is important and should correspond to order of tx's outputs. Empty strings are ignored.") DOC_EXMP_AGGR("ZxDNaMeZjwCjnHuU5gUNyrP1pM3U5vckbakzzV6dEHyDYeCpW8XGLBFTshcaY8LkG9RQn7FsQx8w2JeJzJwPwuDm2NfixPAXf", "ZxBvJDuQjMG9R2j4WnYUhBYNrwZPwuyXrC7FHdVmWqaESgowDvgfWtiXeNGu8Px9B24pkmjsA39fzSSiEQG1ekB225ZnrMTBp") DOC_END + END_KV_SERIALIZE_MAP() + }; + + // TODO consider reusing existing structure transfer_destination -- sowle + struct decoded_output + { + uint64_t amount = 0; + std::string address; + crypto::public_key asset_id; + uint64_t out_index; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(amount) DOC_DSCR("Amount begin transferred.") DOC_EXMP(10000000000000) DOC_END + KV_SERIALIZE(address) DOC_DSCR("Destination address.") DOC_EXMP("ZxBvJDuQjMG9R2j4WnYUhBYNrwZPwuyXrC7FHdVmWqaESgowDvgfWtiXeNGu8Px9B24pkmjsA39fzSSiEQG1ekB225ZnrMTBp") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(asset_id) DOC_DSCR("Asset id.") DOC_EXMP("cc608f59f8080e2fbfe3c8c80eb6e6a953d47cf2d6aebd345bada3a1cab99852") DOC_END + KV_SERIALIZE(out_index) DOC_DSCR("Index of the corresponding output in the transaction.") DOC_EXMP(1) DOC_END + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string status; + std::vector decoded_outputs; + std::string tx_in_json; + crypto::hash verified_tx_id; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) DOC_DSCR("Status code of operation, OK if success") DOC_EXMP(API_RETURN_CODE_OK) DOC_END + KV_SERIALIZE(decoded_outputs) DOC_DSCR("Transaction's decoded outputs") DOC_EXMP_AUTO(1) DOC_END + KV_SERIALIZE_BLOB_AS_BASE64_STRING(tx_in_json) DOC_DSCR("Serialized transaction represented in JSON, encoded in Base64.") DOC_EXMP("ewogICJ2ZXJzaW9uIjogMSwgC....iAgInZpbiI6IFsgewogICAgIC") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(verified_tx_id) DOC_DSCR("(Re)calculated transaction id. Can be used in third-party proof generation.") DOC_EXMP("a6e8da986858e6825fce7a192097e6afae4e889cabe853a9c29b964985b23da8") DOC_END + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_GET_HEIGHT { DOC_COMMAND("Return current blockchain height"); @@ -289,6 +342,58 @@ namespace currency }; }; //----------------------------------------------- + struct COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS + { + DOC_COMMAND("Retrieves information about outputs in recent blocks that are targeted for the given address with the corresponding secret view key.") + + static constexpr uint64_t blocks_limit_default = 5; + + struct request + { + account_public_address address; + crypto::secret_key viewkey; + uint64_t blocks_limit = blocks_limit_default; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_ADDRESS_AS_TEXT(address) DOC_DSCR("Target address for which outputs are being searched") DOC_EXMP("ZxCSpsGGeJsS8fwvQ4HktDU3qBeauoJTR6j73jAWWZxFXdF7XTbGm4YfS2kXJmAP4Rf5BVsSQ9iZ45XANXEYsrLN2L2W77dH7") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(viewkey) DOC_DSCR("Secret view key corresponding to the given address.") DOC_EXMP("5fa8eaaf231a305053260ff91d69c6ef1ecbd0f5") DOC_END + KV_SERIALIZE(blocks_limit) DOC_DSCR("Block count limit. If 0, only the transaction pool will be searched. Maximum and default is " + epee::string_tools::num_to_string_fast(blocks_limit_default) + ".") DOC_EXMP(1711021795) DOC_END + END_KV_SERIALIZE_MAP() + }; + + struct out_entry + { + uint64_t amount; + crypto::public_key asset_id; + crypto::hash tx_id; + int64_t tx_block_height; + uint64_t output_tx_index; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(amount) DOC_DSCR("The amount of the output.") DOC_EXMP(1000000000000) DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(asset_id) DOC_DSCR("Asset ID of the output.") DOC_EXMP("cc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(tx_id) DOC_DSCR("Transaction ID where the output is present, if found.") DOC_EXMP("a6e8da986858e6825fce7a192097e6afae4e889cabe853a9c29b964985b23da8") DOC_END + KV_SERIALIZE(tx_block_height) DOC_DSCR("Block height where the transaction is present.") DOC_EXMP(2555000) DOC_END + KV_SERIALIZE(output_tx_index) DOC_DSCR("Index of the output in the transaction.") DOC_EXMP(2) DOC_END + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::vector outputs; + uint64_t blockchain_top_block_height; + uint64_t blocks_limit; + std::string status; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(outputs) DOC_DSCR("List of found outputs.") DOC_EXMP_AUTO(1) DOC_END + KV_SERIALIZE(blockchain_top_block_height) DOC_DSCR("Height of the most recent block in the blockchain.") DOC_EXMP(2555000) DOC_END + KV_SERIALIZE(blocks_limit) DOC_DSCR("Used limit for block count.") DOC_EXMP(5) DOC_END + KV_SERIALIZE(status) DOC_DSCR("Status of the call.") DOC_EXMP(API_RETURN_CODE_OK) DOC_END + END_KV_SERIALIZE_MAP() + }; + }; + //----------------------------------------------- struct COMMAND_RPC_GET_TX_POOL { DOC_COMMAND("Retreives transactions from tx pool (and other information).") @@ -583,32 +688,32 @@ namespace currency }; //----------------------------------------------- - struct COMMAND_RPC_SEND_RAW_TX - { + struct COMMAND_RPC_SEND_RAW_TX + { DOC_COMMAND("Broadcasts a raw transaction encoded in hexadecimal format to the network."); struct request - { - std::string tx_as_hex; + { + std::string tx_as_hex; - request() {} - explicit request(const transaction &); + request() {} + explicit request(const transaction &); - BEGIN_KV_SERIALIZE_MAP() + BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_as_hex) DOC_DSCR("The transaction data as a hexadecimal string, ready for network broadcast.") DOC_EXMP("00018ed1535b8b4862e.....368cdc5a86") DOC_END END_KV_SERIALIZE_MAP() - }; + }; - struct response - { - std::string status; + struct response + { + std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) DOC_DSCR("Status of the call.") DOC_EXMP(API_RETURN_CODE_OK) DOC_END - END_KV_SERIALIZE_MAP() - }; - }; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) DOC_DSCR("Status of the call.") DOC_EXMP(API_RETURN_CODE_OK) DOC_END + END_KV_SERIALIZE_MAP() + }; + }; //----------------------------------------------- @@ -619,7 +724,7 @@ namespace currency std::vector txs_as_hex; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(txs_as_hex) DOC_DSCR("List of transactions as a hexadecimal strings.") DOC_EXMP_AGGR("000535b8b2e.....3685a86", "00087368b2e.....349b77f") DOC_END + KV_SERIALIZE(txs_as_hex) DOC_DSCR("List of transactions as a hexadecimal strings.") DOC_EXMP_AGGR("000535b8b2e.....3685a86", "00087368b2e.....349b77f") DOC_END END_KV_SERIALIZE_MAP() }; diff --git a/src/serialization/boost_types.h b/src/serialization/boost_types.h index 154705b8..ca3e3d7b 100644 --- a/src/serialization/boost_types.h +++ b/src/serialization/boost_types.h @@ -1,3 +1,4 @@ +// Copyright (c) 2018-2024 Zano Project // Copyright (c) 2014-2017 The The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying @@ -7,7 +8,7 @@ #include - +// boost::optional template