diff options
Diffstat (limited to 'src')
42 files changed, 755 insertions, 299 deletions
diff --git a/src/blockchain_utilities/CMakeLists.txt b/src/blockchain_utilities/CMakeLists.txt index ecd7b754c..6e6e4c6f1 100644 --- a/src/blockchain_utilities/CMakeLists.txt +++ b/src/blockchain_utilities/CMakeLists.txt @@ -26,20 +26,6 @@ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -set(blocksdat "") -if(PER_BLOCK_CHECKPOINT) - if(APPLE AND DEPENDS) - add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && touch stub.c && ${CMAKE_C_COMPILER} --target=x86_64-apple-darwin11 -o stub.o -c stub.c COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -r -sectcreate __DATA __blocks_dat ../blocks/checkpoints.dat -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o stub.o && rm -f stub.*) - elseif(APPLE AND NOT DEPENDS) - add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && touch stub.c && ${CMAKE_C_COMPILER} -o stub.o -c stub.c COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -sectcreate __DATA __blocks_dat ../blocks/checkpoints.dat -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o stub.o && rm -f stub.*) - elseif(LINUX_32) - add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && cp ../blocks/checkpoints.dat blocks.dat && ${CMAKE_LINKER} -m elf_i386 ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o blocks.dat && rm -f blocks.dat) - else() - add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && cp ../blocks/checkpoints.dat blocks.dat && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o blocks.dat && rm -f blocks.dat) - endif() - set(blocksdat "blocksdat.o") -endif() - set(blockchain_import_sources blockchain_import.cpp bootstrap_file.cpp @@ -119,8 +105,7 @@ monero_private_headers(blockchain_depth monero_add_executable(blockchain_import ${blockchain_import_sources} - ${blockchain_import_private_headers} - ${blocksdat}) + ${blockchain_import_private_headers}) target_link_libraries(blockchain_import PRIVATE @@ -132,7 +117,8 @@ target_link_libraries(blockchain_import ${Boost_SYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} - ${EXTRA_LIBRARIES}) + ${EXTRA_LIBRARIES} + ${Blocks}) if(ARCH_WIDTH) target_compile_definitions(blockchain_import @@ -184,7 +170,7 @@ target_link_libraries(blockchain_blackball set_property(TARGET blockchain_blackball PROPERTY - OUTPUT_NAME "monero-blockchain-blackball") + OUTPUT_NAME "monero-blockchain-mark-spent-outputs") install(TARGETS blockchain_blackball DESTINATION bin) diff --git a/src/blockchain_utilities/blockchain_blackball.cpp b/src/blockchain_utilities/blockchain_blackball.cpp index 03ff3cdcd..d2ce5cf76 100644 --- a/src/blockchain_utilities/blockchain_blackball.cpp +++ b/src/blockchain_utilities/blockchain_blackball.cpp @@ -226,7 +226,7 @@ static void init(std::string cache_filename) bool tx_active = false; int dbr; - MINFO("Creating blackball cache in " << cache_filename); + MINFO("Creating spent output cache in " << cache_filename); tools::create_directories_if_necessary(cache_filename); @@ -1019,7 +1019,7 @@ int main(int argc, char* argv[]) po::options_description desc_cmd_only("Command line options"); po::options_description desc_cmd_sett("Command line options and settings options"); const command_line::arg_descriptor<std::string> arg_blackball_db_dir = { - "blackball-db-dir", "Specify blackball database directory", + "spent-output-db-dir", "Specify spent output database directory", get_default_db_path(), }; const command_line::arg_descriptor<std::string> arg_log_level = {"log-level", "0-4 or categories", ""}; @@ -1076,7 +1076,7 @@ int main(int argc, char* argv[]) return 1; } - mlog_configure(mlog_get_default_log_path("monero-blockchain-blackball.log"), true); + mlog_configure(mlog_get_default_log_path("monero-blockchain-find-spent-outputs.log"), true); if (!command_line::is_arg_defaulted(vm, arg_log_level)) mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str()); else @@ -1114,10 +1114,10 @@ int main(int argc, char* argv[]) return 1; } - const std::string cache_dir = (output_file_path / "blackball-cache").string(); + const std::string cache_dir = (output_file_path / "spent-outputs-cache").string(); init(cache_dir); - LOG_PRINT_L0("Scanning for blackballable outputs..."); + LOG_PRINT_L0("Scanning for spent outputs..."); size_t done = 0; @@ -1215,7 +1215,7 @@ int main(int argc, char* argv[]) const std::pair<uint64_t, uint64_t> output = std::make_pair(txin.amount, absolute[0]); if (opt_verbose) { - MINFO("Blackballing output " << output.first << "/" << output.second << ", due to being used in a 1-ring"); + MINFO("Marking output " << output.first << "/" << output.second << " as spent, due to being used in a 1-ring"); std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush; } blackballs.push_back(output); @@ -1229,7 +1229,7 @@ int main(int argc, char* argv[]) const std::pair<uint64_t, uint64_t> output = std::make_pair(txin.amount, absolute[o]); if (opt_verbose) { - MINFO("Blackballing output " << output.first << "/" << output.second << ", due to being used in " << new_ring.size() << " identical " << new_ring.size() << "-rings"); + MINFO("Marking output " << output.first << "/" << output.second << " as spent, due to being used in " << new_ring.size() << " identical " << new_ring.size() << "-rings"); std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush; } blackballs.push_back(output); @@ -1244,7 +1244,7 @@ int main(int argc, char* argv[]) const std::pair<uint64_t, uint64_t> output = std::make_pair(txin.amount, absolute[o]); if (opt_verbose) { - MINFO("Blackballing output " << output.first << "/" << output.second << ", due to being used in " << new_ring.size() << " subsets of " << new_ring.size() << "-rings"); + MINFO("Marking output " << output.first << "/" << output.second << " as spent, due to being used in " << new_ring.size() << " subsets of " << new_ring.size() << "-rings"); std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush; } blackballs.push_back(output); @@ -1280,7 +1280,7 @@ int main(int argc, char* argv[]) const std::pair<uint64_t, uint64_t> output = std::make_pair(txin.amount, common[0]); if (opt_verbose) { - MINFO("Blackballing output " << output.first << "/" << output.second << ", due to being used in rings with a single common element"); + MINFO("Marking output " << output.first << "/" << output.second << " as spent, due to being used in rings with a single common element"); std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush; } blackballs.push_back(output); @@ -1392,7 +1392,7 @@ int main(int argc, char* argv[]) const std::pair<uint64_t, uint64_t> output = std::make_pair(od.amount, last_unknown); if (opt_verbose) { - MINFO("Blackballing output " << output.first << "/" << output.second << ", due to being used in a " << + MINFO("Marking output " << output.first << "/" << output.second << " as spent, due to being used in a " << absolute.size() << "-ring where all other outputs are known to be spent"); } blackballs.push_back(output); @@ -1420,7 +1420,7 @@ int main(int argc, char* argv[]) skip_secondary_passes: uint64_t diff = get_num_spent_outputs() - start_blackballed_outputs; - LOG_PRINT_L0(std::to_string(diff) << " new outputs blackballed, " << get_num_spent_outputs() << " total outputs blackballed"); + LOG_PRINT_L0(std::to_string(diff) << " new outputs marked as spent, " << get_num_spent_outputs() << " total outputs marked as spent"); MDB_txn *txn; dbr = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn); @@ -1460,7 +1460,7 @@ skip_secondary_passes: mdb_txn_abort(txn); } - LOG_PRINT_L0("Blockchain blackball data exported OK"); + LOG_PRINT_L0("Blockchain spent output data exported OK"); close_db(env0, txn0, cur0, dbi0); close(); return 0; diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp index 9ec768d26..7f92ecd87 100644 --- a/src/blockchain_utilities/blockchain_import.cpp +++ b/src/blockchain_utilities/blockchain_import.cpp @@ -37,6 +37,7 @@ #include "misc_log_ex.h" #include "bootstrap_file.h" #include "bootstrap_serialization.h" +#include "blocks/blocks.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "serialization/binary_utils.h" // dump_binary(), parse_binary() #include "serialization/json_utils.h" // dump_json() @@ -758,7 +759,12 @@ int main(int argc, char* argv[]) { core.disable_dns_checkpoints(true); - if (!core.init(vm, NULL)) +#if defined(PER_BLOCK_CHECKPOINT) + const GetCheckpointsCallback& get_checkpoints = blocks::GetCheckpointsData; +#else + const GetCheckpointsCallback& get_checkpoints = nullptr; +#endif + if (!core.init(vm, nullptr, nullptr, get_checkpoints)) { std::cerr << "Failed to initialize core" << ENDL; return 1; diff --git a/src/blocks/CMakeLists.txt b/src/blocks/CMakeLists.txt index ebb5408cc..30d85adbf 100644 --- a/src/blocks/CMakeLists.txt +++ b/src/blocks/CMakeLists.txt @@ -26,20 +26,23 @@ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -if(APPLE) - add_library(blocks STATIC blockexports.c) - set_target_properties(blocks PROPERTIES LINKER_LANGUAGE C) -else() - if(LINUX_32) - add_custom_command(OUTPUT blocks.o MAIN_DEPENDENCY blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -m elf_i386 ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocks.o blocks.dat) - add_custom_command(OUTPUT testnet_blocks.o MAIN_DEPENDENCY testnet_blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -m elf_i386 ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/testnet_blocks.o testnet_blocks.dat) - add_custom_command(OUTPUT stagenet_blocks.o MAIN_DEPENDENCY stagenet_blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -m elf_i386 ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/stagenet_blocks.o stagenet_blocks.dat) - else() - add_custom_command(OUTPUT blocks.o MAIN_DEPENDENCY blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocks.o blocks.dat) - add_custom_command(OUTPUT testnet_blocks.o MAIN_DEPENDENCY testnet_blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/testnet_blocks.o testnet_blocks.dat) - add_custom_command(OUTPUT stagenet_blocks.o MAIN_DEPENDENCY stagenet_blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/stagenet_blocks.o stagenet_blocks.dat) - endif() - add_library(blocks STATIC blocks.o testnet_blocks.o stagenet_blocks.o blockexports.c) - set_target_properties(blocks PROPERTIES LINKER_LANGUAGE C) -endif() +set(GENERATED_SOURCES "") +foreach(BLOB_NAME checkpoints testnet_blocks stagenet_blocks) + set(OUTPUT_C_SOURCE "generated_${BLOB_NAME}.c") + list(APPEND GENERATED_SOURCES ${OUTPUT_C_SOURCE}) + set(INPUT_DAT_FILE "${BLOB_NAME}.dat") + add_custom_command( + OUTPUT ${OUTPUT_C_SOURCE} + MAIN_DEPENDENCY ${INPUT_DAT_FILE} + COMMAND + cd ${CMAKE_CURRENT_BINARY_DIR} && + echo "'#include\t<stddef.h>'" > ${OUTPUT_C_SOURCE} && + echo "'const\tunsigned\tchar\t${BLOB_NAME}[]={'" >> ${OUTPUT_C_SOURCE} && + od -v -An -tu1 ${CMAKE_CURRENT_SOURCE_DIR}/${INPUT_DAT_FILE} | sed -e "'s/[0-9]\\{1,\\}/&,/g'" -e "'$$s/.$$//'" >> ${OUTPUT_C_SOURCE} && + echo "'};'" >> ${OUTPUT_C_SOURCE} && + echo "'const\tsize_t\t${BLOB_NAME}_len\t=\tsizeof(${BLOB_NAME});'" >> ${OUTPUT_C_SOURCE} + ) +endforeach() + +add_library(blocks STATIC blocks.cpp ${GENERATED_SOURCES}) diff --git a/src/blocks/blockexports.c b/src/blocks/blockexports.c deleted file mode 100644 index 0154b0413..000000000 --- a/src/blocks/blockexports.c +++ /dev/null @@ -1,87 +0,0 @@ -#include <stddef.h> - -#if defined(__APPLE__) -#include <mach-o/getsect.h> -#ifdef BUILD_SHARED_LIBS -#if !defined(__LP64__) -const struct mach_header _mh_execute_header; -#else -const struct mach_header_64 _mh_execute_header; -#endif -#else -#if !defined(__LP64__) -extern const struct mach_header _mh_execute_header; -#else -extern const struct mach_header_64 _mh_execute_header; -#endif -#endif - -const unsigned char *get_blocks_dat_start(int testnet, int stagenet) -{ - size_t size; - if (testnet) - return getsectiondata(&_mh_execute_header, "__DATA", "__testnet_blocks_dat", &size); - else if (stagenet) - return getsectiondata(&_mh_execute_header, "__DATA", "__stagenet_blocks_dat", &size); - else - return getsectiondata(&_mh_execute_header, "__DATA", "__blocks_dat", &size); -} - -size_t get_blocks_dat_size(int testnet, int stagenet) -{ - size_t size; - if (testnet) - getsectiondata(&_mh_execute_header, "__DATA", "__testnet_blocks_dat", &size); - else if (stagenet) - getsectiondata(&_mh_execute_header, "__DATA", "__stagenet_blocks_dat", &size); - else - getsectiondata(&_mh_execute_header, "__DATA", "__blocks_dat", &size); - return size; -} - -#else - -#if defined(_WIN32) && !defined(_WIN64) -#define _binary_blocks_start binary_blocks_dat_start -#define _binary_blocks_end binary_blocks_dat_end -#define _binary_testnet_blocks_start binary_testnet_blocks_dat_start -#define _binary_testnet_blocks_end binary_testnet_blocks_dat_end -#define _binary_stagenet_blocks_start binary_stagenet_blocks_dat_start -#define _binary_stagenet_blocks_end binary_stagenet_blocks_dat_end -#else -#define _binary_blocks_start _binary_blocks_dat_start -#define _binary_blocks_end _binary_blocks_dat_end -#define _binary_testnet_blocks_start _binary_testnet_blocks_dat_start -#define _binary_testnet_blocks_end _binary_testnet_blocks_dat_end -#define _binary_stagenet_blocks_start _binary_stagenet_blocks_dat_start -#define _binary_stagenet_blocks_end _binary_stagenet_blocks_dat_end -#endif - -extern const unsigned char _binary_blocks_start[]; -extern const unsigned char _binary_blocks_end[]; -extern const unsigned char _binary_testnet_blocks_start[]; -extern const unsigned char _binary_testnet_blocks_end[]; -extern const unsigned char _binary_stagenet_blocks_start[]; -extern const unsigned char _binary_stagenet_blocks_end[]; - -const unsigned char *get_blocks_dat_start(int testnet, int stagenet) -{ - if (testnet) - return _binary_testnet_blocks_start; - else if (stagenet) - return _binary_stagenet_blocks_start; - else - return _binary_blocks_start; -} - -size_t get_blocks_dat_size(int testnet, int stagenet) -{ - if (testnet) - return (size_t) (_binary_testnet_blocks_end - _binary_testnet_blocks_start); - else if (stagenet) - return (size_t) (_binary_stagenet_blocks_end - _binary_stagenet_blocks_start); - else - return (size_t) (_binary_blocks_end - _binary_blocks_start); -} - -#endif diff --git a/src/blocks/blocks.cpp b/src/blocks/blocks.cpp new file mode 100644 index 000000000..0661f8448 --- /dev/null +++ b/src/blocks/blocks.cpp @@ -0,0 +1,31 @@ +#include "blocks.h" + +#include <unordered_map> + +extern const unsigned char checkpoints[]; +extern const size_t checkpoints_len; +extern const unsigned char stagenet_blocks[]; +extern const size_t stagenet_blocks_len; +extern const unsigned char testnet_blocks[]; +extern const size_t testnet_blocks_len; + +namespace blocks +{ + + const std::unordered_map<cryptonote::network_type, const epee::span<const unsigned char>, std::hash<size_t>> CheckpointsByNetwork = { + {cryptonote::network_type::MAINNET, {checkpoints, checkpoints_len}}, + {cryptonote::network_type::STAGENET, {stagenet_blocks, stagenet_blocks_len}}, + {cryptonote::network_type::TESTNET, {testnet_blocks, testnet_blocks_len}} + }; + + const epee::span<const unsigned char> GetCheckpointsData(cryptonote::network_type network) + { + const auto it = CheckpointsByNetwork.find(network); + if (it != CheckpointsByNetwork.end()) + { + return it->second; + } + return nullptr; + } + +} diff --git a/src/blocks/blocks.dat b/src/blocks/blocks.dat deleted file mode 100644 index e69de29bb..000000000 --- a/src/blocks/blocks.dat +++ /dev/null diff --git a/src/blocks/blocks.h b/src/blocks/blocks.h index ec683f47e..14e391319 100644 --- a/src/blocks/blocks.h +++ b/src/blocks/blocks.h @@ -1,16 +1,12 @@ #ifndef SRC_BLOCKS_BLOCKS_H_ #define SRC_BLOCKS_BLOCKS_H_ -#ifdef __cplusplus -extern "C" { -#endif +#include "cryptonote_config.h" +#include "span.h" -const unsigned char *get_blocks_dat_start(int testnet, int stagenet); -size_t get_blocks_dat_size(int testnet, int stagenet); - -#ifdef __cplusplus +namespace blocks +{ + const epee::span<const unsigned char> GetCheckpointsData(cryptonote::network_type network); } -#endif - #endif /* SRC_BLOCKS_BLOCKS_H_ */ diff --git a/src/common/expect.h b/src/common/expect.h index 326242502..72e4060a7 100644 --- a/src/common/expect.h +++ b/src/common/expect.h @@ -350,7 +350,9 @@ public: using error_type = std::error_code; //! Create a successful object. - expect() = default; + expect() noexcept + : code_() + {} expect(std::error_code const& code) noexcept : code_(code) diff --git a/src/common/spawn.cpp b/src/common/spawn.cpp index 59f11675c..0a2ce8387 100644 --- a/src/common/spawn.cpp +++ b/src/common/spawn.cpp @@ -38,6 +38,7 @@ #endif #include "misc_log_ex.h" +#include "util.h" #include "spawn.h" namespace tools @@ -101,6 +102,8 @@ int spawn(const char *filename, const std::vector<std::string>& args, bool wait) // child if (pid == 0) { + tools::closefrom(3); + close(0); char *envp[] = {NULL}; execve(filename, argv, envp); MERROR("Failed to execve: " << strerror(errno)); diff --git a/src/common/util.cpp b/src/common/util.cpp index 2a1d49af0..43973c511 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -28,6 +28,7 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +#include <unistd.h> #include <cstdio> #ifdef __GLIBC__ @@ -233,7 +234,7 @@ namespace tools MERROR("Failed to open " << filename << ": " << std::error_code(GetLastError(), std::system_category())); } #else - m_fd = open(filename.c_str(), O_RDONLY | O_CREAT, 0666); + m_fd = open(filename.c_str(), O_RDONLY | O_CREAT | O_CLOEXEC, 0666); if (m_fd != -1) { if (flock(m_fd, LOCK_EX | LOCK_NB) == -1) @@ -307,10 +308,19 @@ namespace tools StringCchCopy(pszOS, BUFSIZE, TEXT("Microsoft ")); // Test for the specific product. + if ( osvi.dwMajorVersion == 10 ) + { + if ( osvi.dwMinorVersion == 0 ) + { + if( osvi.wProductType == VER_NT_WORKSTATION ) + StringCchCat(pszOS, BUFSIZE, TEXT("Windows 10 ")); + else StringCchCat(pszOS, BUFSIZE, TEXT("Windows Server 2016 " )); + } + } if ( osvi.dwMajorVersion == 6 ) { - if( osvi.dwMinorVersion == 0 ) + if ( osvi.dwMinorVersion == 0 ) { if( osvi.wProductType == VER_NT_WORKSTATION ) StringCchCat(pszOS, BUFSIZE, TEXT("Windows Vista ")); @@ -324,6 +334,20 @@ namespace tools else StringCchCat(pszOS, BUFSIZE, TEXT("Windows Server 2008 R2 " )); } + if ( osvi.dwMinorVersion == 2 ) + { + if( osvi.wProductType == VER_NT_WORKSTATION ) + StringCchCat(pszOS, BUFSIZE, TEXT("Windows 8 ")); + else StringCchCat(pszOS, BUFSIZE, TEXT("Windows Server 2012 " )); + } + + if ( osvi.dwMinorVersion == 3 ) + { + if( osvi.wProductType == VER_NT_WORKSTATION ) + StringCchCat(pszOS, BUFSIZE, TEXT("Windows 8.1 ")); + else StringCchCat(pszOS, BUFSIZE, TEXT("Windows Server 2012 R2 " )); + } + pGPI = (PGPI) GetProcAddress( GetModuleHandle(TEXT("kernel32.dll")), "GetProductInfo"); @@ -967,4 +991,23 @@ std::string get_nix_version_display_string() } #endif + void closefrom(int fd) + { +#if defined __FreeBSD__ || defined __OpenBSD__ || defined __NetBSD__ || defined __DragonFly__ + ::closefrom(fd); +#else +#if defined __GLIBC__ + const int sc_open_max = sysconf(_SC_OPEN_MAX); + const int MAX_FDS = std::min(65536, sc_open_max); +#else + const int MAX_FDS = 65536; +#endif + while (fd < MAX_FDS) + { + close(fd); + ++fd; + } +#endif + } + } diff --git a/src/common/util.h b/src/common/util.h index ce773bd38..e793a42b5 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -238,4 +238,6 @@ namespace tools #ifdef _WIN32 std::string input_line_win(); #endif + + void closefrom(int fd); } diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index d4558ef7b..b0eabb0aa 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -47,7 +47,6 @@ #include "crypto/crypto.h" #include "crypto/hash.h" #include "misc_language.h" -#include "tx_extra.h" #include "ringct/rctTypes.h" #include "device/device.hpp" diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 9e9c12605..e26aac76b 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -445,6 +445,91 @@ namespace cryptonote return true; } //--------------------------------------------------------------- + template<typename T> + static bool pick(binary_archive<true> &ar, std::vector<tx_extra_field> &fields, uint8_t tag) + { + std::vector<tx_extra_field>::iterator it; + while ((it = std::find_if(fields.begin(), fields.end(), [](const tx_extra_field &f) { return f.type() == typeid(T); })) != fields.end()) + { + bool r = ::do_serialize(ar, tag); + CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to serialize tx extra field"); + r = ::do_serialize(ar, boost::get<T>(*it)); + CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to serialize tx extra field"); + fields.erase(it); + } + return true; + } + //--------------------------------------------------------------- + bool sort_tx_extra(const std::vector<uint8_t>& tx_extra, std::vector<uint8_t> &sorted_tx_extra, bool allow_partial) + { + std::vector<tx_extra_field> tx_extra_fields; + + if(tx_extra.empty()) + { + sorted_tx_extra.clear(); + return true; + } + + std::string extra_str(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size()); + std::istringstream iss(extra_str); + binary_archive<false> ar(iss); + + bool eof = false; + size_t processed = 0; + while (!eof) + { + tx_extra_field field; + bool r = ::do_serialize(ar, field); + if (!r) + { + MWARNING("failed to deserialize extra field. extra = " << string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size()))); + if (!allow_partial) + return false; + break; + } + tx_extra_fields.push_back(field); + processed = iss.tellg(); + + std::ios_base::iostate state = iss.rdstate(); + eof = (EOF == iss.peek()); + iss.clear(state); + } + if (!::serialization::check_stream_state(ar)) + { + MWARNING("failed to deserialize extra field. extra = " << string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size()))); + if (!allow_partial) + return false; + } + MTRACE("Sorted " << processed << "/" << tx_extra.size()); + + std::ostringstream oss; + binary_archive<true> nar(oss); + + // sort by: + if (!pick<tx_extra_pub_key>(nar, tx_extra_fields, TX_EXTRA_TAG_PUBKEY)) return false; + if (!pick<tx_extra_additional_pub_keys>(nar, tx_extra_fields, TX_EXTRA_TAG_ADDITIONAL_PUBKEYS)) return false; + if (!pick<tx_extra_nonce>(nar, tx_extra_fields, TX_EXTRA_NONCE)) return false; + if (!pick<tx_extra_merge_mining_tag>(nar, tx_extra_fields, TX_EXTRA_MERGE_MINING_TAG)) return false; + if (!pick<tx_extra_mysterious_minergate>(nar, tx_extra_fields, TX_EXTRA_MYSTERIOUS_MINERGATE_TAG)) return false; + if (!pick<tx_extra_padding>(nar, tx_extra_fields, TX_EXTRA_TAG_PADDING)) return false; + + // if not empty, someone added a new type and did not add a case above + if (!tx_extra_fields.empty()) + { + MERROR("tx_extra_fields not empty after sorting, someone forgot to add a case above"); + return false; + } + + std::string oss_str = oss.str(); + if (allow_partial && processed < tx_extra.size()) + { + MDEBUG("Appending unparsed data"); + oss_str += std::string((const char*)tx_extra.data() + processed, tx_extra.size() - processed); + } + sorted_tx_extra = std::vector<uint8_t>(oss_str.begin(), oss_str.end()); + return true; + } + //--------------------------------------------------------------- crypto::public_key get_tx_pub_key_from_extra(const std::vector<uint8_t>& tx_extra, size_t pk_index) { std::vector<tx_extra_field> tx_extra_fields; diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index 725c75f4e..8d33b1ca4 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -31,6 +31,7 @@ #pragma once #include "blobdatatype.h" #include "cryptonote_basic_impl.h" +#include "tx_extra.h" #include "account.h" #include "subaddress_index.h" #include "include_base_utils.h" @@ -65,6 +66,7 @@ namespace cryptonote } bool parse_tx_extra(const std::vector<uint8_t>& tx_extra, std::vector<tx_extra_field>& tx_extra_fields); + bool sort_tx_extra(const std::vector<uint8_t>& tx_extra, std::vector<uint8_t> &sorted_tx_extra, bool allow_partial = false); crypto::public_key get_tx_pub_key_from_extra(const std::vector<uint8_t>& tx_extra, size_t pk_index = 0); crypto::public_key get_tx_pub_key_from_extra(const transaction_prefix& tx, size_t pk_index = 0); crypto::public_key get_tx_pub_key_from_extra(const transaction& tx, size_t pk_index = 0); diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index 2bd43de94..d0b03593e 100644 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -201,7 +201,7 @@ namespace cryptonote float hr = static_cast<float>(total_hr)/static_cast<float>(m_last_hash_rates.size()); const auto flags = std::cout.flags(); const auto precision = std::cout.precision(); - std::cout << "hashrate: " << std::setprecision(4) << std::fixed << hr << flags << precision << ENDL; + std::cout << "hashrate: " << std::setprecision(4) << std::fixed << hr << std::setiosflags(flags) << std::setprecision(precision) << ENDL; } } m_last_hr_merge_time = misc_utils::get_tick_count(); diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index a6858ce7c..c62eeb738 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -30,6 +30,7 @@ #pragma once +#include <stdexcept> #include <string> #include <boost/uuid/uuid.hpp> diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 72844db66..231489fdb 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -41,12 +41,6 @@ set(cryptonote_core_private_headers tx_pool.h cryptonote_tx_utils.h) -if(PER_BLOCK_CHECKPOINT) - set(Blocks "blocks") -else() - set(Blocks "") -endif() - monero_private_headers(cryptonote_core ${cryptonote_core_private_headers}) monero_add_library(cryptonote_core @@ -69,5 +63,4 @@ target_link_libraries(cryptonote_core ${Boost_SYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} PRIVATE - ${Blocks} ${EXTRA_LIBRARIES}) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index eb869b795..77b6d0b69 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -53,9 +53,6 @@ #include "ringct/rctSigs.h" #include "common/perf_timer.h" #include "common/notify.h" -#if defined(PER_BLOCK_CHECKPOINT) -#include "blocks/blocks.h" -#endif #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "blockchain" @@ -341,9 +338,12 @@ uint64_t Blockchain::get_current_blockchain_height() const //------------------------------------------------------------------ //FIXME: possibly move this into the constructor, to avoid accidentally // dereferencing a null BlockchainDB pointer -bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline, const cryptonote::test_options *test_options, difficulty_type fixed_difficulty) +bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline, const cryptonote::test_options *test_options, difficulty_type fixed_difficulty, const GetCheckpointsCallback& get_checkpoints/* = nullptr*/) { LOG_PRINT_L3("Blockchain::" << __func__); + + CHECK_AND_ASSERT_MES(nettype != FAKECHAIN || test_options, false, "fake chain network type used without options"); + CRITICAL_REGION_LOCAL(m_tx_pool); CRITICAL_REGION_LOCAL1(m_blockchain_lock); @@ -439,7 +439,7 @@ bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline #if defined(PER_BLOCK_CHECKPOINT) if (m_nettype != FAKECHAIN) - load_compiled_in_block_hashes(); + load_compiled_in_block_hashes(get_checkpoints); #endif MINFO("Blockchain initialized. last block: " << m_db->height() - 1 << ", " << epee::misc_utils::get_time_interval_string(timestamp_diff) << " time ago, current difficulty: " << get_difficulty_for_next_block()); @@ -883,6 +883,15 @@ difficulty_type Blockchain::get_difficulty_for_next_block() return diff; } //------------------------------------------------------------------ +std::vector<time_t> Blockchain::get_last_block_timestamps(unsigned int blocks) const +{ + std::vector<time_t> timestamps(blocks); + uint64_t height = m_db->height(); + while (blocks--) + timestamps[blocks] = m_db->get_block_timestamp(height - blocks - 1); + return timestamps; +} +//------------------------------------------------------------------ // This function removes blocks from the blockchain until it gets to the // position where the blockchain switch started and then re-adds the blocks // that had been removed. @@ -2364,7 +2373,7 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context const bool bulletproof = rct::is_rct_bulletproof(tx.rct_signatures.type); if (bulletproof || !tx.rct_signatures.p.bulletproofs.empty()) { - MERROR("Bulletproofs are not allowed before v8"); + MERROR_VER("Bulletproofs are not allowed before v8"); tvc.m_invalid_output = true; return false; } @@ -2377,7 +2386,7 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context const bool borromean = rct::is_rct_borromean(tx.rct_signatures.type); if (borromean) { - MERROR("Borromean range proofs are not allowed after v8"); + MERROR_VER("Borromean range proofs are not allowed after v8"); tvc.m_invalid_output = true; return false; } @@ -4404,19 +4413,21 @@ void Blockchain::cancel() #if defined(PER_BLOCK_CHECKPOINT) static const char expected_block_hashes_hash[] = "954cb2bbfa2fe6f74b2cdd22a1a4c767aea249ad47ad4f7c9445f0f03260f511"; -void Blockchain::load_compiled_in_block_hashes() +void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get_checkpoints) { - const bool testnet = m_nettype == TESTNET; - const bool stagenet = m_nettype == STAGENET; - if (m_fast_sync && get_blocks_dat_start(testnet, stagenet) != nullptr && get_blocks_dat_size(testnet, stagenet) > 0) + if (get_checkpoints == nullptr || !m_fast_sync) { - MINFO("Loading precomputed blocks (" << get_blocks_dat_size(testnet, stagenet) << " bytes)"); - + return; + } + const epee::span<const unsigned char> &checkpoints = get_checkpoints(m_nettype); + if (!checkpoints.empty()) + { + MINFO("Loading precomputed blocks (" << checkpoints.size() << " bytes)"); if (m_nettype == MAINNET) { // first check hash crypto::hash hash; - if (!tools::sha256sum(get_blocks_dat_start(testnet, stagenet), get_blocks_dat_size(testnet, stagenet), hash)) + if (!tools::sha256sum(checkpoints.data(), checkpoints.size(), hash)) { MERROR("Failed to hash precomputed blocks data"); return; @@ -4436,9 +4447,9 @@ void Blockchain::load_compiled_in_block_hashes() } } - if (get_blocks_dat_size(testnet, stagenet) > 4) + if (checkpoints.size() > 4) { - const unsigned char *p = get_blocks_dat_start(testnet, stagenet); + const unsigned char *p = checkpoints.data(); const uint32_t nblocks = *p | ((*(p+1))<<8) | ((*(p+2))<<16) | ((*(p+3))<<24); if (nblocks > (std::numeric_limits<uint32_t>::max() - 4) / sizeof(hash)) { @@ -4446,7 +4457,7 @@ void Blockchain::load_compiled_in_block_hashes() return; } const size_t size_needed = 4 + nblocks * sizeof(crypto::hash); - if(nblocks > 0 && nblocks > (m_db->height() + HASH_OF_HASHES_STEP - 1) / HASH_OF_HASHES_STEP && get_blocks_dat_size(testnet, stagenet) >= size_needed) + if(nblocks > 0 && nblocks > (m_db->height() + HASH_OF_HASHES_STEP - 1) / HASH_OF_HASHES_STEP && checkpoints.size() >= size_needed) { p += sizeof(uint32_t); m_blocks_hash_of_hashes.reserve(nblocks); diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index ab66fac8b..f140d7719 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -38,9 +38,11 @@ #include <boost/multi_index/hashed_index.hpp> #include <boost/multi_index/member.hpp> #include <atomic> +#include <functional> #include <unordered_map> #include <unordered_set> +#include "span.h" #include "syncobj.h" #include "string_tools.h" #include "cryptonote_basic/cryptonote_basic.h" @@ -73,6 +75,15 @@ namespace cryptonote db_nosync //!< Leave syncing up to the backing db (safest, but slowest because of disk I/O) }; + /** + * @brief Callback routine that returns checkpoints data for specific network type + * + * @param network network type + * + * @return checkpoints data, empty span if there ain't any checkpoints for specific network type + */ + typedef std::function<const epee::span<const unsigned char>(cryptonote::network_type network)> GetCheckpointsCallback; + /************************************************************************/ /* */ /************************************************************************/ @@ -117,10 +128,11 @@ namespace cryptonote * @param offline true if running offline, else false * @param test_options test parameters * @param fixed_difficulty fixed difficulty for testing purposes; 0 means disabled + * @param get_checkpoints if set, will be called to get checkpoints data * * @return true on success, false if any initialization steps fail */ - bool init(BlockchainDB* db, const network_type nettype = MAINNET, bool offline = false, const cryptonote::test_options *test_options = NULL, difficulty_type fixed_difficulty = 0); + bool init(BlockchainDB* db, const network_type nettype = MAINNET, bool offline = false, const cryptonote::test_options *test_options = NULL, difficulty_type fixed_difficulty = 0, const GetCheckpointsCallback& get_checkpoints = nullptr); /** * @brief Initialize the Blockchain state @@ -952,6 +964,11 @@ namespace cryptonote */ void on_new_tx_from_block(const cryptonote::transaction &tx); + /** + * @brief returns the timestamps of the last N blocks + */ + std::vector<time_t> get_last_block_timestamps(unsigned int blocks) const; + private: // TODO: evaluate whether or not each of these typedefs are left over from blockchain_storage @@ -1369,8 +1386,10 @@ namespace cryptonote * A (possibly empty) set of block hashes can be compiled into the * monero daemon binary. This function loads those hashes into * a useful state. + * + * @param get_checkpoints if set, will be called to get checkpoints data */ - void load_compiled_in_block_hashes(); + void load_compiled_in_block_hashes(const GetCheckpointsCallback& get_checkpoints); /** * @brief expands v2 transaction data from blockchain diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 69e3c708b..d8c38bf9e 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -389,7 +389,7 @@ namespace cryptonote return m_blockchain_storage.get_alternative_blocks_count(); } //----------------------------------------------------------------------------------------------- - bool core::init(const boost::program_options::variables_map& vm, const char *config_subdir, const cryptonote::test_options *test_options) + bool core::init(const boost::program_options::variables_map& vm, const char *config_subdir, const cryptonote::test_options *test_options, const GetCheckpointsCallback& get_checkpoints/* = nullptr */) { start_time = std::time(nullptr); @@ -567,7 +567,7 @@ namespace cryptonote regtest_hard_forks }; const difficulty_type fixed_difficulty = command_line::get_arg(vm, arg_fixed_difficulty); - r = m_blockchain_storage.init(db.release(), m_nettype, m_offline, regtest ? ®test_test_options : test_options, fixed_difficulty); + r = m_blockchain_storage.init(db.release(), m_nettype, m_offline, regtest ? ®test_test_options : test_options, fixed_difficulty, get_checkpoints); r = m_mempool.init(max_txpool_weight); CHECK_AND_ASSERT_MES(r, false, "Failed to initialize memory pool"); @@ -856,16 +856,19 @@ namespace cryptonote } waiter.wait(&tpool); it = tx_blobs.begin(); + std::vector<bool> already_have(tx_blobs.size(), false); for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { if (!results[i].res) continue; if(m_mempool.have_tx(results[i].hash)) { LOG_PRINT_L2("tx " << results[i].hash << "already have transaction in tx_pool"); + already_have[i] = true; } else if(m_blockchain_storage.have_tx(results[i].hash)) { LOG_PRINT_L2("tx " << results[i].hash << " already have transaction in blockchain"); + already_have[i] = true; } else { @@ -887,7 +890,7 @@ namespace cryptonote std::vector<tx_verification_batch_info> tx_info; tx_info.reserve(tx_blobs.size()); for (size_t i = 0; i < tx_blobs.size(); i++) { - if (!results[i].res) + if (!results[i].res || already_have[i]) continue; tx_info.push_back({&results[i].tx, results[i].hash, tvc[i], results[i].res}); } @@ -897,6 +900,8 @@ namespace cryptonote bool ok = true; it = tx_blobs.begin(); for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { + if (already_have[i]) + continue; if (!results[i].res) { ok = false; @@ -1489,6 +1494,7 @@ namespace cryptonote m_txpool_auto_relayer.do_call(boost::bind(&core::relay_txpool_transactions, this)); m_check_updates_interval.do_call(boost::bind(&core::check_updates, this)); m_check_disk_space_interval.do_call(boost::bind(&core::check_disk_space, this)); + m_block_rate_interval.do_call(boost::bind(&core::check_block_rate, this)); m_miner.on_idle(); m_mempool.on_idle(); return true; @@ -1677,6 +1683,52 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + double factorial(unsigned int n) + { + if (n <= 1) + return 1.0; + double f = n; + while (n-- > 1) + f *= n; + return f; + } + //----------------------------------------------------------------------------------------------- + static double probability(unsigned int blocks, unsigned int expected) + { + // https://www.umass.edu/wsp/resources/poisson/#computing + return pow(expected, blocks) / (factorial(blocks) * exp(expected)); + } + //----------------------------------------------------------------------------------------------- + bool core::check_block_rate() + { + if (m_offline || m_target_blockchain_height > get_current_blockchain_height()) + { + MDEBUG("Not checking block rate, offline or syncing"); + return true; + } + + static constexpr double threshold = 1. / (864000 / DIFFICULTY_TARGET_V2); // one false positive every 10 days + + const time_t now = time(NULL); + const std::vector<time_t> timestamps = m_blockchain_storage.get_last_block_timestamps(60); + + static const unsigned int seconds[] = { 5400, 1800, 600 }; + for (size_t n = 0; n < sizeof(seconds)/sizeof(seconds[0]); ++n) + { + unsigned int b = 0; + for (time_t ts: timestamps) b += ts >= now - seconds[n]; + const double p = probability(b, seconds[n] / DIFFICULTY_TARGET_V2); + MDEBUG("blocks in the last " << seconds[n] / 60 << " minutes: " << b << " (probability " << p << ")"); + if (p < threshold) + { + MWARNING("There were " << b << " blocks in the last " << seconds[n] / 60 << " minutes, there might be large hash rate changes, or we might be partitioned, cut off from the Monero network or under attack. Or it could be just sheer bad luck."); + break; // no need to look further + } + } + + return true; + } + //----------------------------------------------------------------------------------------------- void core::set_target_blockchain_height(uint64_t target_blockchain_height) { m_target_blockchain_height = target_blockchain_height; diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 58fe5b7b5..80c452f53 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -244,10 +244,11 @@ namespace cryptonote * @param vm command line parameters * @param config_subdir subdirectory for config storage * @param test_options configuration options for testing + * @param get_checkpoints if set, will be called to get checkpoints data, must return checkpoints data pointer and size or nullptr if there ain't any checkpoints for specific network type * * @return false if one of the init steps fails, otherwise true */ - bool init(const boost::program_options::variables_map& vm, const char *config_subdir = NULL, const test_options *test_options = NULL); + bool init(const boost::program_options::variables_map& vm, const char *config_subdir = NULL, const test_options *test_options = NULL, const GetCheckpointsCallback& get_checkpoints = nullptr); /** * @copydoc Blockchain::reset_and_set_genesis_block @@ -945,6 +946,13 @@ namespace cryptonote */ bool check_disk_space(); + /** + * @brief checks block rate, and warns if it's too slow + * + * @return true on success, false otherwise + */ + bool check_block_rate(); + bool m_test_drop_download = true; //!< whether or not to drop incoming blocks (for testing) uint64_t m_test_drop_download_height = 0; //!< height under which to drop incoming blocks, if doing so @@ -969,6 +977,7 @@ namespace cryptonote epee::math_helper::once_a_time_seconds<60*2, false> m_txpool_auto_relayer; //!< interval for checking re-relaying txpool transactions epee::math_helper::once_a_time_seconds<60*60*12, true> m_check_updates_interval; //!< interval for checking for new versions epee::math_helper::once_a_time_seconds<60*10, true> m_check_disk_space_interval; //!< interval for checking for disk space + epee::math_helper::once_a_time_seconds<90, false> m_block_rate_interval; //!< interval for checking block rate std::atomic<bool> m_starter_message_showed; //!< has the "daemon will sync now" message been shown? diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 4bc33b56b..4fc2736a6 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -38,6 +38,7 @@ using namespace epee; #include "cryptonote_tx_utils.h" #include "cryptonote_config.h" #include "cryptonote_basic/miner.h" +#include "cryptonote_basic/tx_extra.h" #include "crypto/crypto.h" #include "crypto/hash.h" #include "ringct/rctSigs.h" @@ -84,6 +85,8 @@ namespace cryptonote if(!extra_nonce.empty()) if(!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) return false; + if (!sort_tx_extra(tx.extra, tx.extra)) + return false; txin_gen in; in.height = height; @@ -434,6 +437,9 @@ namespace cryptonote add_additional_tx_pub_keys_to_extra(tx.extra, additional_tx_public_keys); } + if (!sort_tx_extra(tx.extra, tx.extra)) + return false; + //check money if(summary_outs_money > summary_inputs_money ) { diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 5a9fcf67e..553a22298 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -765,7 +765,7 @@ namespace cryptonote m_blockchain.for_all_txpool_txes([&tx_infos, key_image_infos, include_sensitive_data](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ tx_info txi; txi.id_hash = epee::string_tools::pod_to_hex(txid); - txi.tx_blob = epee::string_tools::buff_to_hex_nodelimer(*bd); + txi.tx_blob = *bd; transaction tx; if (!parse_and_validate_tx_from_blob(*bd, tx)) { diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index f645836a4..117790455 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -26,20 +26,6 @@ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -set(blocksdat "") -if(PER_BLOCK_CHECKPOINT) - if(APPLE AND DEPENDS) - add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && touch stub.c && ${CMAKE_C_COMPILER} --target=x86_64-apple-darwin11 -o stub.o -c stub.c COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -r -sectcreate __DATA __blocks_dat ../blocks/checkpoints.dat -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o stub.o && rm -f stub.*) - elseif(APPLE AND NOT DEPENDS) - add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && touch stub.c && ${CMAKE_C_COMPILER} -o stub.o -c stub.c COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -sectcreate __DATA __blocks_dat ../blocks/checkpoints.dat -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o stub.o && rm -f stub.*) - elseif(LINUX_32) - add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && cp ../blocks/checkpoints.dat blocks.dat && ${CMAKE_LINKER} -m elf_i386 ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o blocks.dat && rm -f blocks.dat) - else() - add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && cp ../blocks/checkpoints.dat blocks.dat && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o blocks.dat && rm -f blocks.dat) - endif() - set(blocksdat "blocksdat.o") -endif() - set(daemon_sources command_parser_executor.cpp command_server.cpp @@ -81,9 +67,7 @@ monero_private_headers(daemon monero_add_executable(daemon ${daemon_sources} ${daemon_headers} - ${daemon_private_headers} - ${blocksdat} -) + ${daemon_private_headers}) target_link_libraries(daemon PRIVATE rpc @@ -106,7 +90,8 @@ target_link_libraries(daemon ${CMAKE_THREAD_LIBS_INIT} ${ZMQ_LIB} ${GNU_READLINE_LIBRARY} - ${EXTRA_LIBRARIES}) + ${EXTRA_LIBRARIES} + ${Blocks}) set_property(TARGET daemon PROPERTY OUTPUT_NAME "monerod") diff --git a/src/daemon/core.h b/src/daemon/core.h index 475f418d6..d1defd573 100644 --- a/src/daemon/core.h +++ b/src/daemon/core.h @@ -28,6 +28,7 @@ #pragma once +#include "blocks/blocks.h" #include "cryptonote_core/cryptonote_core.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" #include "misc_log_ex.h" @@ -85,7 +86,12 @@ public: //initialize core here MGINFO("Initializing core..."); std::string config_subdir = get_config_subdir(); - if (!m_core.init(m_vm_HACK, config_subdir.empty() ? NULL : config_subdir.c_str())) +#if defined(PER_BLOCK_CHECKPOINT) + const cryptonote::GetCheckpointsCallback& get_checkpoints = blocks::GetCheckpointsData; +#else + const cryptonote::GetCheckpointsCallback& get_checkpoints = nullptr; +#endif + if (!m_core.init(m_vm_HACK, config_subdir.empty() ? NULL : config_subdir.c_str(), nullptr, get_checkpoints)) { return false; } diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index f53649518..49d6d49cf 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -137,13 +137,18 @@ bool t_daemon::run(bool interactive) throw std::runtime_error{"Can't run stopped daemon"}; } - std::atomic<bool> stop(false); - boost::thread([&stop, this] { + std::atomic<bool> stop(false), shutdown(false); + boost::thread stop_thread = boost::thread([&stop, &shutdown, this] { while (!stop) epee::misc_utils::sleep_no_w(100); - this->stop_p2p(); - }).detach(); - tools::signal_handler::install([&stop](int){ stop = true; }); + if (shutdown) + this->stop_p2p(); + }); + epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ + stop = true; + stop_thread.join(); + }); + tools::signal_handler::install([&stop, &shutdown](int){ stop = shutdown = true; }); try { diff --git a/src/daemonizer/windows_daemonizer.inl b/src/daemonizer/windows_daemonizer.inl index 8077f29fb..7e61e3603 100644 --- a/src/daemonizer/windows_daemonizer.inl +++ b/src/daemonizer/windows_daemonizer.inl @@ -31,6 +31,7 @@ #include "common/util.h" #include "daemonizer/windows_service.h" #include "daemonizer/windows_service_runner.h" +#include "cryptonote_core/cryptonote_core.h" #include <shlobj.h> #include <boost/filesystem/operations.hpp> diff --git a/src/device/CMakeLists.txt b/src/device/CMakeLists.txt index 8f446f42a..727134f75 100644 --- a/src/device/CMakeLists.txt +++ b/src/device/CMakeLists.txt @@ -58,12 +58,6 @@ endif() set(device_private_headers) -if(PER_BLOCK_CHECKPOINT) - set(Blocks "blocks") -else() - set(Blocks "") -endif() - monero_private_headers(device ${device_private_headers}) @@ -79,5 +73,4 @@ target_link_libraries(device ringct_basic ${OPENSSL_CRYPTO_LIBRARIES} PRIVATE - ${Blocks} ${EXTRA_LIBRARIES}) diff --git a/src/device/device_io_hid.cpp b/src/device/device_io_hid.cpp index 562aca8b8..666255cb3 100644 --- a/src/device/device_io_hid.cpp +++ b/src/device/device_io_hid.cpp @@ -13,6 +13,7 @@ // #if defined(HAVE_HIDAPI) +#include <boost/scope_exit.hpp> #include "log.hpp" #include "device_io_hid.hpp" @@ -69,11 +70,47 @@ namespace hw { void device_io_hid::connect(void *params) { hid_conn_params *p = (struct hid_conn_params*)params; - this->connect(p->vid, p->pid, p->interface_number, p->usage_page, p->interface_OR_page); + this->connect(p->vid, p->pid, p->interface_number, p->usage_page); } - void device_io_hid::connect(unsigned int vid, unsigned int pid, unsigned int interface_number, unsigned int usage_page, bool interface_OR_page ) { - hid_device_info *hwdev_info, *hwdev_info_list; + hid_device_info *device_io_hid::find_device(hid_device_info *devices_list, boost::optional<int> interface_number, boost::optional<unsigned short> usage_page) { + bool select_any = !interface_number && !usage_page; + + MDEBUG( "Looking for " << + (select_any ? "any HID Device" : "HID Device with") << + (interface_number ? (" interface_number " + std::to_string(interface_number.value())) : "") << + ((interface_number && usage_page) ? " or" : "") << + (usage_page ? (" usage_page " + std::to_string(usage_page.value())) : "")); + + hid_device_info *result = nullptr; + for (; devices_list != nullptr; devices_list = devices_list->next) { + BOOST_SCOPE_EXIT(&devices_list, &result) { + MDEBUG( (result == devices_list ? "SELECTED" : "SKIPPED ") << + " HID Device" << + " path " << safe_hid_path(devices_list) << + " interface_number " << devices_list->interface_number << + " usage_page " << devices_list->usage_page); + } + BOOST_SCOPE_EXIT_END + + if (result != nullptr) { + continue; + } + + if (select_any) { + result = devices_list; + } else if (interface_number && devices_list->interface_number == interface_number.value()) { + result = devices_list; + } else if (usage_page && devices_list->usage_page == usage_page.value()) { + result = devices_list; + } + } + + return result; + } + + void device_io_hid::connect(unsigned int vid, unsigned int pid, boost::optional<int> interface_number, boost::optional<unsigned short> usage_page) { + hid_device_info *hwdev_info_list; hid_device *hwdev; this->disconnect(); @@ -81,17 +118,8 @@ namespace hw { hwdev_info_list = hid_enumerate(vid, pid); ASSERT_X(hwdev_info_list, "Unable to enumerate device "+std::to_string(vid)+":"+std::to_string(vid)+ ": "+ safe_hid_error(this->usb_device)); hwdev = NULL; - hwdev_info = hwdev_info_list; - while (hwdev_info) { - if ((interface_OR_page && ((usage_page == 0xffa0) || (interface_number == 0))) || - ((usage_page == 0xffa0) && (interface_number == 0)) ) { - MDEBUG("HID Device found: " << safe_hid_path(hwdev_info)); - hwdev = hid_open_path(hwdev_info->path); - break; - } else { - MDEBUG("HID Device discard: " << safe_hid_path(hwdev_info) << "("+std::to_string(hwdev_info->usage_page) << "," << std::to_string(hwdev_info->interface_number) << ")"); - } - hwdev_info = hwdev_info->next; + if (hid_device_info *device = find_device(hwdev_info_list, interface_number, usage_page)) { + hwdev = hid_open_path(device->path); } hid_free_enumeration(hwdev_info_list); ASSERT_X(hwdev, "Unable to open device "+std::to_string(pid)+":"+std::to_string(vid)); diff --git a/src/device/device_io_hid.hpp b/src/device/device_io_hid.hpp index 6fd15a1d1..bb0f0a814 100644 --- a/src/device/device_io_hid.hpp +++ b/src/device/device_io_hid.hpp @@ -29,6 +29,7 @@ #if defined(HAVE_HIDAPI) +#include <boost/optional/optional.hpp> #include <hidapi/hidapi.h> #include "device_io.hpp" @@ -52,9 +53,8 @@ namespace hw { struct hid_conn_params { unsigned int vid; unsigned int pid; - unsigned int interface_number; - unsigned int usage_page; - bool interface_OR_page ; + int interface_number; + unsigned short usage_page; }; @@ -82,13 +82,11 @@ namespace hw { unsigned int wrapCommand(const unsigned char *command, size_t command_len, unsigned char *out, size_t out_len); unsigned int unwrapReponse(const unsigned char *data, size_t data_len, unsigned char *out, size_t out_len); + hid_device_info *find_device(hid_device_info *devices_list, boost::optional<int> interface_number, boost::optional<unsigned short> usage_page); public: bool hid_verbose = false; - static const unsigned int OR_SELECT = 1; - static const unsigned int AND_SELECT = 2; - static const unsigned short DEFAULT_CHANNEL = 0x0001; static const unsigned char DEFAULT_TAG = 0x01; static const unsigned int DEFAULT_PACKET_SIZE = 64; @@ -100,7 +98,7 @@ namespace hw { void init(); void connect(void *params); - void connect(unsigned int vid, unsigned int pid, unsigned int interface_number, unsigned int usage_page, bool interface_OR_page ); + void connect(unsigned int vid, unsigned int pid, boost::optional<int> interface_number, boost::optional<unsigned short> usage_page); bool connected() const; int exchange(unsigned char *command, unsigned int cmd_len, unsigned char *response, unsigned int max_resp_len); void disconnect(); diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index a17784960..d879ee95a 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -340,7 +340,7 @@ namespace hw { bool device_ledger::connect(void) { this->disconnect(); - hw_device.connect(0x2c97,0x0001, 0, 0xffa0, hw_device.OR_SELECT); + hw_device.connect(0x2c97, 0x0001, 0, 0xffa0); this->reset(); #ifdef DEBUG_HWDEVICE cryptonote::account_public_address pubkey; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index d8f556d3e..aa9d3d64b 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -212,23 +212,15 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ - static cryptonote::blobdata get_pruned_tx_blob(cryptonote::transaction &tx) - { - std::stringstream ss; - binary_archive<true> ba(ss); - bool r = tx.serialize_base(ba); - CHECK_AND_ASSERT_MES(r, cryptonote::blobdata(), "Failed to serialize rct signatures base"); - return ss.str(); - } - //------------------------------------------------------------------------------------------------------------------------------ - static cryptonote::blobdata get_pruned_tx_json(cryptonote::transaction &tx) - { - std::stringstream ss; - json_archive<true> ar(ss); - bool r = tx.serialize_base(ar); - CHECK_AND_ASSERT_MES(r, cryptonote::blobdata(), "Failed to serialize rct signatures base"); - return ss.str(); - } + class pruned_transaction { + transaction& tx; + public: + pruned_transaction(transaction& tx) : tx(tx) {} + BEGIN_SERIALIZE_OBJECT() + bool r = tx.serialize_base(ar); + if (!r) return false; + END_SERIALIZE() + }; //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_blocks(const COMMAND_RPC_GET_BLOCKS_FAST::request& req, COMMAND_RPC_GET_BLOCKS_FAST::response& res) { @@ -564,10 +556,11 @@ namespace cryptonote crypto::hash tx_hash = *vhi++; e.tx_hash = *txhi++; - blobdata blob = req.prune ? get_pruned_tx_blob(tx) : t_serializable_object_to_blob(tx); + pruned_transaction pruned_tx{tx}; + blobdata blob = req.prune ? t_serializable_object_to_blob(pruned_tx) : t_serializable_object_to_blob(tx); e.as_hex = string_tools::buff_to_hex_nodelimer(blob); if (req.decode_as_json) - e.as_json = req.prune ? get_pruned_tx_json(tx) : obj_to_json_str(tx); + e.as_json = req.prune ? obj_to_json_str(pruned_tx) : obj_to_json_str(tx); e.in_pool = pool_tx_hashes.find(tx_hash) != pool_tx_hashes.end(); if (e.in_pool) { @@ -924,6 +917,8 @@ namespace cryptonote return r; m_core.get_pool_transactions_and_spent_keys_info(res.transactions, res.spent_key_images, !request_has_rpc_origin || !m_restricted); + for (tx_info& txi : res.transactions) + txi.tx_blob = epee::string_tools::buff_to_hex_nodelimer(txi.tx_blob); res.status = CORE_RPC_STATUS_OK; return true; } @@ -2118,6 +2113,8 @@ namespace cryptonote try { + // 0 is placeholder for the whole chain + const uint64_t req_to_height = req.to_height ? req.to_height : (m_core.get_current_blockchain_height() - 1); for (uint64_t amount: req.amounts) { static struct D @@ -2130,7 +2127,7 @@ namespace cryptonote } d; boost::unique_lock<boost::mutex> lock(d.mutex); - if (d.cached && amount == 0 && d.cached_from == req.from_height && d.cached_to == req.to_height) + if (d.cached && amount == 0 && d.cached_from == req.from_height && d.cached_to == req_to_height) { res.distributions.push_back({amount, d.cached_start_height, req.binary, d.cached_distribution, d.cached_base}); if (!req.cumulative) @@ -2145,23 +2142,23 @@ namespace cryptonote std::vector<uint64_t> distribution; uint64_t start_height, base; - if (!m_core.get_output_distribution(amount, req.from_height, req.to_height, start_height, distribution, base)) + if (!m_core.get_output_distribution(amount, req.from_height, req_to_height, start_height, distribution, base)) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Failed to get rct distribution"; return false; } - if (req.to_height > 0 && req.to_height >= req.from_height) + if (req_to_height > 0 && req_to_height >= req.from_height) { uint64_t offset = std::max(req.from_height, start_height); - if (offset <= req.to_height && req.to_height - offset + 1 < distribution.size()) - distribution.resize(req.to_height - offset + 1); + if (offset <= req_to_height && req_to_height - offset + 1 < distribution.size()) + distribution.resize(req_to_height - offset + 1); } if (amount == 0) { d.cached_from = req.from_height; - d.cached_to = req.to_height; + d.cached_to = req_to_height; d.cached_distribution = distribution; d.cached_start_height = start_height; d.cached_base = base; diff --git a/src/rpc/daemon_messages.h b/src/rpc/daemon_messages.h index 8fff369df..be3138e3b 100644 --- a/src/rpc/daemon_messages.h +++ b/src/rpc/daemon_messages.h @@ -67,7 +67,7 @@ class classname \ // NOTE: when using a type with multiple template parameters, // replace any comma in the template specifier with the macro // above, or the preprocessor will eat the comma in a bad way. -#define RPC_MESSAGE_MEMBER(type, name) type name; +#define RPC_MESSAGE_MEMBER(type, name) type name = type{} namespace cryptonote diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 47b77abc6..3d92b2823 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -1640,7 +1640,7 @@ bool simple_wallet::blackball(const std::vector<std::string> &args) uint64_t amount = std::numeric_limits<uint64_t>::max(), offset, num_offsets; if (args.size() == 0) { - fail_msg_writer() << tr("usage: blackball <amount>/<offset> | <filename> [add]"); + fail_msg_writer() << tr("usage: mark_output_spent <amount>/<offset> | <filename> [add]"); return true; } @@ -1718,7 +1718,7 @@ bool simple_wallet::blackball(const std::vector<std::string> &args) } catch (const std::exception &e) { - fail_msg_writer() << tr("Failed to blackball output: ") << e.what(); + fail_msg_writer() << tr("Failed to mark output spent: ") << e.what(); } return true; @@ -1729,7 +1729,7 @@ bool simple_wallet::unblackball(const std::vector<std::string> &args) std::pair<uint64_t, uint64_t> output; if (args.size() != 1) { - fail_msg_writer() << tr("usage: unblackball <amount>/<offset>"); + fail_msg_writer() << tr("usage: mark_output_unspent <amount>/<offset>"); return true; } @@ -1745,7 +1745,7 @@ bool simple_wallet::unblackball(const std::vector<std::string> &args) } catch (const std::exception &e) { - fail_msg_writer() << tr("Failed to unblackball output: ") << e.what(); + fail_msg_writer() << tr("Failed to mark output unspent: ") << e.what(); } return true; @@ -1756,7 +1756,7 @@ bool simple_wallet::blackballed(const std::vector<std::string> &args) std::pair<uint64_t, uint64_t> output; if (args.size() != 1) { - fail_msg_writer() << tr("usage: blackballed <amount>/<offset>"); + fail_msg_writer() << tr("usage: is_output_spent <amount>/<offset>"); return true; } @@ -1769,13 +1769,13 @@ bool simple_wallet::blackballed(const std::vector<std::string> &args) try { if (m_wallet->is_output_blackballed(output)) - message_writer() << tr("Blackballed: ") << output.first << "/" << output.second; + message_writer() << tr("Spent: ") << output.first << "/" << output.second; else - message_writer() << tr("not blackballed: ") << output.first << "/" << output.second; + message_writer() << tr("Not spent: ") << output.first << "/" << output.second; } catch (const std::exception &e) { - fail_msg_writer() << tr("Failed to unblackball output: ") << e.what(); + fail_msg_writer() << tr("Failed to check whether output is spent: ") << e.what(); } return true; @@ -2599,18 +2599,18 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::save_known_rings, this, _1), tr("save_known_rings"), tr("Save known rings to the shared rings database")); - m_cmd_binder.set_handler("blackball", + m_cmd_binder.set_handler("mark_output_spent", boost::bind(&simple_wallet::blackball, this, _1), - tr("blackball <amount>/<offset> | <filename> [add]"), - tr("Blackball output(s) so they never get selected as fake outputs in a ring")); - m_cmd_binder.set_handler("unblackball", + tr("mark_output_spent <amount>/<offset> | <filename> [add]"), + tr("Mark output(s) as spent so they never get selected as fake outputs in a ring")); + m_cmd_binder.set_handler("mark_output_unspent", boost::bind(&simple_wallet::unblackball, this, _1), - tr("unblackball <amount>/<offset>"), - tr("Unblackballs an output so it may get selected as a fake output in a ring")); - m_cmd_binder.set_handler("blackballed", + tr("mark_output_unspent <amount>/<offset>"), + tr("Marks an output as unspent so it may get selected as a fake output in a ring")); + m_cmd_binder.set_handler("is_output_spent", boost::bind(&simple_wallet::blackballed, this, _1), - tr("blackballed <amount>/<offset>"), - tr("Checks whether an output is blackballed")); + tr("is_output_spent <amount>/<offset>"), + tr("Checks whether an output is marked as spent")); m_cmd_binder.set_handler("version", boost::bind(&simple_wallet::version, this, _1), tr("version"), @@ -4715,8 +4715,6 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (!try_connect_to_daemon()) return true; - SCOPED_WALLET_UNLOCK(); - std::vector<std::string> local_args = args_; std::set<uint32_t> subaddr_indices; @@ -4936,6 +4934,8 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri } } + SCOPED_WALLET_UNLOCK(); + try { // figure out what tx will be necessary @@ -7050,12 +7050,6 @@ bool simple_wallet::run() void simple_wallet::stop() { m_cmd_binder.stop_handling(); - - m_idle_run.store(false, std::memory_order_relaxed); - m_wallet->stop(); - // make the background refresh thread quit - boost::unique_lock<boost::mutex> lock(m_idle_mutex); - m_idle_cond.notify_one(); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector<std::string>()*/) diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index de1bfdae1..1b4370c36 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -776,7 +776,7 @@ bool WalletImpl::setPassword(const std::string &password) { clearStatus(); try { - m_wallet->rewrite(m_wallet->get_wallet_file(), password); + m_wallet->change_password(m_wallet->get_wallet_file(), m_password, password); m_password = password; } catch (const std::exception &e) { setStatusError(e.what()); @@ -2161,7 +2161,7 @@ bool WalletImpl::blackballOutputs(const std::vector<std::string> &outputs, bool bool ret = m_wallet->set_blackballed_outputs(raw_outputs, add); if (!ret) { - setStatusError(tr("Failed to set blackballed outputs")); + setStatusError(tr("Failed to mark outputs as spent")); return false; } return true; @@ -2183,7 +2183,7 @@ bool WalletImpl::blackballOutput(const std::string &amount, const std::string &o bool ret = m_wallet->blackball_output(std::make_pair(raw_amount, raw_offset)); if (!ret) { - setStatusError(tr("Failed to blackball output")); + setStatusError(tr("Failed to mark output as spent")); return false; } return true; @@ -2205,7 +2205,7 @@ bool WalletImpl::unblackballOutput(const std::string &amount, const std::string bool ret = m_wallet->unblackball_output(std::make_pair(raw_amount, raw_offset)); if (!ret) { - setStatusError(tr("Failed to unblackball output")); + setStatusError(tr("Failed to mark output as unspent")); return false; } return true; diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index e5995e7fb..f562d6c06 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -412,13 +412,13 @@ bool ringdb::blackball_worker(const std::vector<std::pair<uint64_t, uint64_t>> & switch (op) { case BLACKBALL_BLACKBALL: - MDEBUG("Blackballing output " << output.first << "/" << output.second); + MDEBUG("Marking output " << output.first << "/" << output.second << " as spent"); dbr = mdb_cursor_put(cursor, &key, &data, MDB_APPENDDUP); if (dbr == MDB_KEYEXIST) dbr = 0; break; case BLACKBALL_UNBLACKBALL: - MDEBUG("Unblackballing output " << output.first << "/" << output.second); + MDEBUG("Marking output " << output.first << "/" << output.second << " as unspent"); dbr = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH); if (dbr == 0) dbr = mdb_cursor_del(cursor, 0); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index d774c54c0..fd79da4e9 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -37,6 +37,8 @@ #include <boost/algorithm/string/classification.hpp> #include <boost/algorithm/string/trim.hpp> #include <boost/algorithm/string/split.hpp> +#include <boost/algorithm/string/join.hpp> +#include <boost/range/adaptor/transformed.hpp> #include "include_base_utils.h" using namespace epee; @@ -121,6 +123,8 @@ using namespace cryptonote; #define FIRST_REFRESH_GRANULARITY 1024 +#define GAMMA_PICK_HALF_WINDOW 5 + static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1"; static const std::string MULTISIG_EXTRA_INFO_MAGIC = "MultisigxV1"; @@ -5608,6 +5612,10 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin ptx.construction_data = sd; txs.push_back(ptx); + + // add tx keys only to ptx + txs.back().tx_key = tx_key; + txs.back().additional_tx_keys = additional_tx_keys; } // add key images @@ -6793,10 +6801,29 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> error::get_output_distribution, "Decreasing offsets in rct distribution: " + std::to_string(block_offset) + ": " + std::to_string(rct_offsets[block_offset]) + ", " + std::to_string(block_offset + 1) + ": " + std::to_string(rct_offsets[block_offset + 1])); - uint64_t n_rct = rct_offsets[block_offset + 1] - rct_offsets[block_offset]; + uint64_t first_block_offset = block_offset, last_block_offset = block_offset; + for (size_t half_window = 0; half_window < GAMMA_PICK_HALF_WINDOW; ++half_window) + { + // end when we have a non empty block + uint64_t cum0 = first_block_offset > 0 ? rct_offsets[first_block_offset] - rct_offsets[first_block_offset - 1] : rct_offsets[0]; + if (cum0 > 1) + break; + uint64_t cum1 = last_block_offset > 0 ? rct_offsets[last_block_offset] - rct_offsets[last_block_offset - 1] : rct_offsets[0]; + if (cum1 > 1) + break; + if (first_block_offset == 0 && last_block_offset >= rct_offsets.size() - 2) + break; + // expand up to bounds + if (first_block_offset > 0) + --first_block_offset; + if (last_block_offset < rct_offsets.size() - 1) + ++last_block_offset; + } + const uint64_t n_rct = rct_offsets[last_block_offset] - (first_block_offset == 0 ? 0 : rct_offsets[first_block_offset - 1]); if (n_rct == 0) return rct_offsets[block_offset] ? rct_offsets[block_offset] - 1 : 0; - return rct_offsets[block_offset] + crypto::rand<uint64_t>() % n_rct; + MDEBUG("Picking 1/" << n_rct << " in " << (last_block_offset - first_block_offset + 1) << " blocks centered around " << block_offset); + return rct_offsets[first_block_offset] + crypto::rand<uint64_t>() % n_rct; }; size_t num_selected_transfers = 0; @@ -6966,6 +6993,8 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> LOG_PRINT_L1("Selecting real output: " << td.m_global_output_index << " for " << print_money(amount)); } + std::unordered_map<const char*, std::set<uint64_t>> picks; + // while we still need more mixins uint64_t num_usable_outs = num_outs; bool allow_blackballed = false; @@ -6980,7 +7009,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // outputs, we still need to reach the minimum ring size) if (allow_blackballed) break; - MINFO("Not enough non blackballed outputs, we'll allow blackballed ones"); + MINFO("Not enough output not marked as spent, we'll allow outputs marked as spent"); allow_blackballed = true; num_usable_outs = num_outs; } @@ -7064,11 +7093,15 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> } seen_indices.emplace(i); - LOG_PRINT_L2("picking " << i << " as " << type); + picks[type].insert(i); req.outputs.push_back({amount, i}); ++num_found; } + for (const auto &pick: picks) + MDEBUG("picking " << pick.first << " outputs: " << + boost::join(pick.second | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " ")); + // if we had enough unusable outputs, we might fall off here and still // have too few outputs, so we stuff with one to keep counts good, and // we'll error out later @@ -7084,8 +7117,15 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> [](const get_outputs_out &a, const get_outputs_out &b) { return a.index < b.index; }); } - for (auto i: req.outputs) - LOG_PRINT_L1("asking for output " << i.index << " for " << print_money(i.amount)); + if (ELPP->vRegistry()->allowed(el::Level::Debug, MONERO_DEFAULT_LOG_CATEGORY)) + { + std::map<uint64_t, std::set<uint64_t>> outs; + for (const auto &i: req.outputs) + outs[i.amount].insert(i.index); + for (const auto &o: outs) + MDEBUG("asking for outputs with amount " << print_money(o.first) << ": " << + boost::join(o.second | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " ")); + } // get the keys for those m_daemon_rpc_mutex.lock(); @@ -10543,7 +10583,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag + boost::lexical_cast<std::string>(signed_key_images.size()) + ", key image " + epee::string_tools::pod_to_hex(key_image)); THROW_WALLET_EXCEPTION_IF(!crypto::check_ring_signature((const crypto::hash&)key_image, key_image, pkeys, &signature), - error::wallet_internal_error, "Signature check failed: input " + boost::lexical_cast<std::string>(n) + "/" + error::signature_check_failed, boost::lexical_cast<std::string>(n) + "/" + boost::lexical_cast<std::string>(signed_key_images.size()) + ", key image " + epee::string_tools::pod_to_hex(key_image) + ", signature " + epee::string_tools::pod_to_hex(signature) + ", pubkey " + epee::string_tools::pod_to_hex(*pkeys[0])); diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index bc518d04a..b3141985d 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -72,6 +72,7 @@ namespace tools // tx_parse_error // get_tx_pool_error // out_of_hashchain_bounds_error + // signature_check_failed // transfer_error * // get_outs_general_error // not_enough_unlocked_money @@ -418,6 +419,14 @@ namespace tools std::string to_string() const { return refresh_error::to_string(); } }; //---------------------------------------------------------------------------------------------------- + struct signature_check_failed : public wallet_logic_error + { + explicit signature_check_failed(std::string&& loc, const std::string& message) + : wallet_logic_error(std::move(loc), "Signature check failed " + message) + { + } + }; + //---------------------------------------------------------------------------------------------------- struct transfer_error : public wallet_logic_error { protected: diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index e0b631aaf..6dbea2e14 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -981,6 +981,8 @@ namespace tools for (auto &ptx: ptxs) { res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); + if (req.get_tx_keys) + res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); } if (req.export_raw) @@ -994,6 +996,171 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_describe_transfer(const wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_restricted) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (m_wallet->key_on_device()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "command not supported by HW wallet"; + return false; + } + if(m_wallet->watch_only()) + { + er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; + er.message = "command not supported by watch-only wallet"; + return false; + } + + tools::wallet2::unsigned_tx_set exported_txs; + try + { + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(req.unsigned_txset, blob)) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_HEX; + er.message = "Failed to parse hex."; + return false; + } + if(!m_wallet->parse_unsigned_tx_from_str(blob, exported_txs)) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA; + er.message = "cannot load unsigned_txset"; + return false; + } + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA; + er.message = "failed to parse unsigned transfers: " + std::string(e.what()); + return false; + } + + std::vector<tools::wallet2::pending_tx> ptx; + try + { + // gather info to ask the user + std::unordered_map<cryptonote::account_public_address, std::pair<std::string, uint64_t>> dests; + int first_known_non_zero_change_index = -1; + for (size_t n = 0; n < exported_txs.txes.size(); ++n) + { + const tools::wallet2::tx_construction_data &cd = exported_txs.txes[n]; + res.desc.push_back({0, 0, std::numeric_limits<uint32_t>::max(), 0, {}, "", 0, "", 0, 0, ""}); + wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::transfer_description &desc = res.desc.back(); + + std::vector<cryptonote::tx_extra_field> tx_extra_fields; + bool has_encrypted_payment_id = false; + crypto::hash8 payment_id8 = crypto::null_hash8; + if (cryptonote::parse_tx_extra(cd.extra, tx_extra_fields)) + { + cryptonote::tx_extra_nonce extra_nonce; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + crypto::hash payment_id; + if(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) + { + desc.payment_id = epee::string_tools::pod_to_hex(payment_id8); + has_encrypted_payment_id = true; + } + else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) + { + desc.payment_id = epee::string_tools::pod_to_hex(payment_id); + } + } + } + + for (size_t s = 0; s < cd.sources.size(); ++s) + { + desc.amount_in += cd.sources[s].amount; + size_t ring_size = cd.sources[s].outputs.size(); + if (ring_size < desc.ring_size) + desc.ring_size = ring_size; + } + for (size_t d = 0; d < cd.splitted_dsts.size(); ++d) + { + const cryptonote::tx_destination_entry &entry = cd.splitted_dsts[d]; + std::string address = cryptonote::get_account_address_as_str(m_wallet->nettype(), entry.is_subaddress, entry.addr); + if (has_encrypted_payment_id && !entry.is_subaddress) + address = cryptonote::get_account_integrated_address_as_str(m_wallet->nettype(), entry.addr, payment_id8); + auto i = dests.find(entry.addr); + if (i == dests.end()) + dests.insert(std::make_pair(entry.addr, std::make_pair(address, entry.amount))); + else + i->second.second += entry.amount; + desc.amount_out += entry.amount; + } + if (cd.change_dts.amount > 0) + { + auto it = dests.find(cd.change_dts.addr); + if (it == dests.end()) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA; + er.message = "Claimed change does not go to a paid address"; + return false; + } + if (it->second.second < cd.change_dts.amount) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA; + er.message = "Claimed change is larger than payment to the change address"; + return false; + } + if (cd.change_dts.amount > 0) + { + if (first_known_non_zero_change_index == -1) + first_known_non_zero_change_index = n; + const tools::wallet2::tx_construction_data &cdn = exported_txs.txes[first_known_non_zero_change_index]; + if (memcmp(&cd.change_dts.addr, &cdn.change_dts.addr, sizeof(cd.change_dts.addr))) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA; + er.message = "Change goes to more than one address"; + return false; + } + } + desc.change_amount += cd.change_dts.amount; + it->second.second -= cd.change_dts.amount; + if (it->second.second == 0) + dests.erase(cd.change_dts.addr); + } + + size_t n_dummy_outputs = 0; + for (auto i = dests.begin(); i != dests.end(); ) + { + if (i->second.second > 0) + { + desc.recipients.push_back({i->second.first, i->second.second}); + } + else + ++desc.dummy_outputs; + ++i; + } + + if (desc.change_amount > 0) + { + const tools::wallet2::tx_construction_data &cd0 = exported_txs.txes[0]; + desc.change_address = get_account_address_as_str(m_wallet->nettype(), cd0.subaddr_account > 0, cd0.change_dts.addr); + } + + desc.fee = desc.amount_in - desc.amount_out; + desc.unlock_time = cd.unlock_time; + desc.extra = epee::to_hex::string({cd.extra.data(), cd.extra.size()}); + } + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA; + er.message = "failed to parse unsigned transfers"; + return false; + } + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_submit_transfer(const wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er) { if (!m_wallet) return not_open(er); @@ -2822,8 +2989,7 @@ namespace tools { try { - m_wallet->rewrite(m_wallet->get_wallet_file(), req.new_password); - m_wallet->store(); + m_wallet->change_password(m_wallet->get_wallet_file(), req.old_password, req.new_password); LOG_PRINT_L0("Wallet password changed."); } catch (const std::exception& e) @@ -2906,6 +3072,11 @@ namespace tools er.code = WALLET_RPC_ERROR_CODE_ADDRESS_INDEX_OUT_OF_BOUNDS; er.message = e.what(); } + catch (const error::signature_check_failed& e) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_SIGNATURE; + er.message = e.what(); + } catch (const std::exception& e) { er.code = default_error_code; diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 35ea11902..887723ed5 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -87,6 +87,7 @@ namespace tools MAP_JON_RPC_WE("transfer", on_transfer, wallet_rpc::COMMAND_RPC_TRANSFER) MAP_JON_RPC_WE("transfer_split", on_transfer_split, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT) MAP_JON_RPC_WE("sign_transfer", on_sign_transfer, wallet_rpc::COMMAND_RPC_SIGN_TRANSFER) + MAP_JON_RPC_WE("describe_transfer", on_describe_transfer, wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER) MAP_JON_RPC_WE("submit_transfer", on_submit_transfer, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER) MAP_JON_RPC_WE("sweep_dust", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST) MAP_JON_RPC_WE("sweep_unmixable", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST) @@ -167,6 +168,7 @@ namespace tools bool on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er); bool on_transfer_split(const wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::request& req, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::response& res, epee::json_rpc::error& er); bool on_sign_transfer(const wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::response& res, epee::json_rpc::error& er); + bool on_describe_transfer(const wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::response& res, epee::json_rpc::error& er); bool on_submit_transfer(const wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er); bool on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er); bool on_sweep_all(const wallet_rpc::COMMAND_RPC_SWEEP_ALL::request& req, wallet_rpc::COMMAND_RPC_SWEEP_ALL::response& res, epee::json_rpc::error& er); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 2377b69e3..924f3a0f1 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -47,7 +47,7 @@ // advance which version they will stop working with // Don't go over 32767 for any of these #define WALLET_RPC_VERSION_MAJOR 1 -#define WALLET_RPC_VERSION_MINOR 4 +#define WALLET_RPC_VERSION_MINOR 5 #define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR) namespace tools @@ -531,16 +531,79 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_DESCRIBE_TRANSFER + { + struct recipient + { + std::string address; + uint64_t amount; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + KV_SERIALIZE(amount) + END_KV_SERIALIZE_MAP() + }; + + struct transfer_description + { + uint64_t amount_in; + uint64_t amount_out; + uint32_t ring_size; + uint64_t unlock_time; + std::list<recipient> recipients; + std::string payment_id; + uint64_t change_amount; + std::string change_address; + uint64_t fee; + uint32_t dummy_outputs; + std::string extra; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(amount_in) + KV_SERIALIZE(amount_out) + KV_SERIALIZE(ring_size) + KV_SERIALIZE(unlock_time) + KV_SERIALIZE(recipients) + KV_SERIALIZE(payment_id) + KV_SERIALIZE(change_amount) + KV_SERIALIZE(change_address) + KV_SERIALIZE(fee) + KV_SERIALIZE(dummy_outputs) + KV_SERIALIZE(extra) + END_KV_SERIALIZE_MAP() + }; + + struct request + { + std::string unsigned_txset; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(unsigned_txset) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::list<transfer_description> desc; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(desc) + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_SIGN_TRANSFER { struct request { std::string unsigned_txset; bool export_raw; + bool get_tx_keys; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(unsigned_txset) KV_SERIALIZE_OPT(export_raw, false) + KV_SERIALIZE_OPT(get_tx_keys, false) END_KV_SERIALIZE_MAP() }; @@ -549,11 +612,13 @@ namespace wallet_rpc std::string signed_txset; std::list<std::string> tx_hash_list; std::list<std::string> tx_raw_list; + std::list<std::string> tx_key_list; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(signed_txset) KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_raw_list) + KV_SERIALIZE(tx_key_list) END_KV_SERIALIZE_MAP() }; }; |