From 4363a9f1001893c80ee2435399836cfe43b3014e Mon Sep 17 00:00:00 2001 From: Antonio Juarez Date: Wed, 13 Aug 2014 11:51:37 +0100 Subject: [PATCH] Multi-signatures added --- CMakeLists.txt | 3 +- ReleaseNotes.txt | 12 + contrib/CMakeLists.txt | 7 + contrib/epee/demo/demo_levin_server/stdafx.h | 3 - contrib/epee/include/console_handler.h | 59 +- contrib/epee/include/file_io_utils.h | 13 +- contrib/epee/include/math_helper.h | 1 + contrib/epee/include/misc_log_ex.cpp | 1029 +++++++++++ contrib/epee/include/misc_log_ex.h | 1175 ++---------- contrib/epee/include/misc_os_dependent.cpp | 97 + contrib/epee/include/misc_os_dependent.h | 84 +- .../epee/include/net/abstract_tcp_server2.inl | 4 +- .../epee/include/net/http_protocol_handler.h | 1 + .../include/net/http_protocol_handler.inl | 2 + .../include/net/http_server_handlers_map2.h | 9 +- .../net/levin_protocol_handler_async.h | 1 + contrib/epee/include/net/net_parse_helpers.h | 1 + contrib/epee/include/net/net_utils_base.h | 5 + contrib/epee/include/profile_tools.h | 2 + .../keyvalue_serialization_overloads.h | 5 + contrib/epee/include/static_initializer.h | 27 - .../epee/include/storages/portable_storage.h | 7 +- .../storages/portable_storage_from_json.h | 4 + .../storages/portable_storage_to_bin.h | 1 + .../portable_storage_val_converters.h | 4 +- contrib/epee/include/string_tools.cpp | 487 +++++ contrib/epee/include/string_tools.h | 543 +----- contrib/epee/include/syncobj.h | 7 +- external/google/dense_hash_map | 333 ++++ external/google/dense_hash_set | 308 ++++ external/google/sparse_hash_map | 310 ++++ external/google/sparse_hash_set | 285 +++ external/google/sparsehash/densehashtable.h | 1268 +++++++++++++ external/google/sparsehash/hashtable-common.h | 178 ++ .../sparsehash/libc_allocator_with_realloc.h | 121 ++ external/google/sparsehash/os_config.h | 37 + external/google/sparsehash/sparseconfig.h | 39 + external/google/sparsehash/sparseconfig_win.h | 37 + external/google/sparsehash/sparsehashtable.h | 1190 ++++++++++++ external/google/sparsetable | 1598 +++++++++++++++++ external/google/type_traits.h | 336 ++++ include/INode.h | 2 +- include/IWallet.h | 4 +- src/CMakeLists.txt | 11 +- src/common/BlockingQueue.cpp | 18 + src/common/BlockingQueue.h | 126 ++ src/common/ObserverManager.h | 1 + src/common/SignalHandler.cpp | 54 + src/common/SignalHandler.h | 59 + src/common/base58.cpp | 1 - src/common/boost_serialization_helper.h | 9 + ...unordered_containers_boost_serialization.h | 67 + src/common/util.cpp | 22 +- src/common/util.h | 76 +- src/connectivity_tool/conn_tool.cpp | 27 +- src/crypto/hash-ops.h | 3 + src/crypto/hash.h | 8 + src/crypto/tree-hash.c | 72 + src/cryptonote_config.h | 166 +- src/cryptonote_core/AccountKVSerialization.h | 113 ++ src/cryptonote_core/BlockIndex.cpp | 87 + src/cryptonote_core/BlockIndex.h | 90 + src/cryptonote_core/Currency.cpp | 435 +++++ src/cryptonote_core/Currency.h | 238 +++ src/cryptonote_core/ITimeProvider.cpp | 18 + src/cryptonote_core/ITimeProvider.h | 35 + src/cryptonote_core/ITransactionValidator.h | 51 + src/cryptonote_core/SwappedVector.h | 135 +- src/cryptonote_core/UpgradeDetector.cpp | 18 + src/cryptonote_core/UpgradeDetector.h | 193 ++ src/cryptonote_core/account.cpp | 19 +- src/cryptonote_core/account.h | 31 +- .../account_boost_serialization.h | 6 +- src/cryptonote_core/blockchain_storage.cpp | 1097 ++++++----- src/cryptonote_core/blockchain_storage.h | 185 +- src/cryptonote_core/checkpoints_create.h | 46 - src/cryptonote_core/connection_context.h | 7 +- src/cryptonote_core/cryptonote_basic.h | 551 +++--- src/cryptonote_core/cryptonote_basic_impl.cpp | 190 +- src/cryptonote_core/cryptonote_basic_impl.h | 32 +- .../cryptonote_boost_serialization.h | 80 +- src/cryptonote_core/cryptonote_core.cpp | 252 +-- src/cryptonote_core/cryptonote_core.h | 79 +- .../cryptonote_format_utils.cpp | 539 +++--- src/cryptonote_core/cryptonote_format_utils.h | 118 +- src/cryptonote_core/difficulty.cpp | 46 - src/cryptonote_core/difficulty.h | 2 - src/cryptonote_core/i_miner_handler.h | 31 + src/cryptonote_core/miner.cpp | 136 +- src/cryptonote_core/miner.h | 45 +- src/cryptonote_core/tx_extra.h | 6 + src/cryptonote_core/tx_pool.cpp | 501 +++--- src/cryptonote_core/tx_pool.h | 188 +- src/cryptonote_core/verification_context.h | 1 + .../cryptonote_protocol_defs.h | 124 +- .../cryptonote_protocol_handler.inl | 70 +- .../cryptonote_protocol_handler_common.h | 25 +- src/daemon/daemon.cpp | 53 +- src/daemon/daemon_commands_handler.h | 36 +- src/node_rpc_proxy/NodeRpcProxy.cpp | 4 +- src/node_rpc_proxy/NodeRpcProxy.h | 4 +- src/p2p/net_node.h | 7 +- src/p2p/net_node.inl | 64 +- src/p2p/net_node_common.h | 2 +- src/p2p/net_peerlist.h | 6 +- src/p2p/p2p_protocol_defs.h | 4 + src/rpc/core_rpc_server.cpp | 185 +- src/rpc/core_rpc_server.h | 9 +- src/rpc/core_rpc_server_commands_defs.h | 135 +- src/simplewallet/simplewallet.cpp | 287 +-- src/simplewallet/simplewallet.h | 20 +- src/version.h.in | 4 +- src/wallet/Wallet.cpp | 46 +- src/wallet/Wallet.h | 16 +- src/wallet/WalletRequest.h | 8 +- src/wallet/WalletSendTransactionContext.h | 4 +- src/wallet/WalletSerialization.h | 9 +- src/wallet/WalletSynchronizationContext.h | 2 +- src/wallet/WalletSynchronizer.cpp | 57 +- src/wallet/WalletSynchronizer.h | 14 +- src/wallet/WalletTransactionSender.cpp | 56 +- src/wallet/WalletTransactionSender.h | 10 +- src/wallet/WalletTransferDetails.cpp | 28 +- src/wallet/WalletTransferDetails.h | 6 +- src/wallet/WalletUnconfirmedTransactions.cpp | 2 +- src/wallet/WalletUnconfirmedTransactions.h | 4 +- src/wallet/WalletUserTransactionsCache.cpp | 20 +- src/wallet/WalletUserTransactionsCache.h | 8 +- src/wallet/wallet2.cpp | 457 +++-- src/wallet/wallet2.h | 129 +- src/wallet/wallet_errors.h | 68 +- src/wallet/wallet_rpc_server.cpp | 10 +- tests/CMakeLists.txt | 33 +- tests/TestGenerator/TestGenerator.cpp | 367 ++++ tests/TestGenerator/TestGenerator.h | 115 ++ tests/core_proxy/core_proxy.cpp | 72 +- tests/core_proxy/core_proxy.h | 37 +- tests/core_tests/TestGenerator.h | 119 ++ tests/core_tests/TransactionBuilder.cpp | 186 ++ tests/core_tests/TransactionBuilder.h | 75 + tests/core_tests/block_reward.cpp | 151 +- tests/core_tests/block_reward.h | 2 +- tests/core_tests/block_validation.cpp | 480 +++-- tests/core_tests/block_validation.h | 281 ++- tests/core_tests/chain_split_1.cpp | 15 +- tests/core_tests/chain_switch_1.cpp | 29 +- tests/core_tests/chain_switch_1.h | 4 +- tests/core_tests/chaingen.cpp | 384 +--- tests/core_tests/chaingen.h | 265 ++- tests/core_tests/chaingen001.cpp | 27 +- .../{chaingen_tests_list.h => chaingen001.h} | 15 +- tests/core_tests/chaingen_main.cpp | 113 +- tests/core_tests/double_spend.cpp | 319 +++- tests/core_tests/double_spend.h | 108 +- tests/core_tests/double_spend.inl | 40 +- tests/core_tests/integer_overflow.cpp | 65 +- tests/core_tests/integer_overflow.h | 4 +- tests/core_tests/ring_signature_1.cpp | 67 +- tests/core_tests/transaction_tests.cpp | 70 +- tests/core_tests/transaction_tests.h | 2 - tests/core_tests/tx_validation.cpp | 413 +++-- tests/core_tests/tx_validation.h | 51 +- tests/core_tests/upgrade.cpp | 251 +++ tests/core_tests/upgrade.h | 49 + tests/difficulty/difficulty.cpp | 21 +- tests/functional_tests/main.cpp | 1 + .../transactions_flow_test.cpp | 40 +- ...ransactions_generation_from_blockchain.cpp | 6 +- tests/net_load_tests/clt.cpp | 2 + tests/net_load_tests/net_load_tests.h | 1 + tests/net_load_tests/srv.cpp | 3 + .../node_rpc_proxy_test.cpp | 2 +- .../performance_tests/check_ring_signature.h | 6 +- tests/performance_tests/construct_tx.h | 2 +- tests/performance_tests/derive_public_key.h | 4 +- tests/performance_tests/derive_secret_key.h | 2 +- tests/performance_tests/generate_key_image.h | 4 +- .../generate_key_image_helper.h | 3 +- tests/performance_tests/is_out_to_acc.h | 3 +- tests/performance_tests/multi_tx_test_base.h | 9 +- tests/performance_tests/single_tx_test_base.h | 5 +- tests/unit_tests/BlockingQueue.cpp | 211 +++ tests/unit_tests/INodeStubs.cpp | 10 +- tests/unit_tests/INodeStubs.h | 6 +- tests/unit_tests/TestBlockchainGenerator.cpp | 42 +- tests/unit_tests/TestBlockchainGenerator.h | 19 +- tests/unit_tests/TestUpgradeDetector.cpp | 309 ++++ tests/unit_tests/base58.cpp | 62 +- tests/unit_tests/block_reward.cpp | 474 +++-- tests/unit_tests/get_xtype_from_string.cpp | 3 + tests/unit_tests/main.cpp | 4 + tests/unit_tests/parse_amount.cpp | 9 +- tests/unit_tests/serialization.cpp | 24 +- tests/unit_tests/test_format_utils.cpp | 40 +- tests/unit_tests/test_protocol_pack.cpp | 2 + tests/unit_tests/test_wallet.cpp | 82 +- tests/unit_tests/tx_pool.cpp | 397 ++++ 197 files changed, 17996 insertions(+), 5974 deletions(-) create mode 100644 contrib/CMakeLists.txt create mode 100644 contrib/epee/include/misc_log_ex.cpp create mode 100644 contrib/epee/include/misc_os_dependent.cpp create mode 100644 contrib/epee/include/string_tools.cpp create mode 100644 external/google/dense_hash_map create mode 100644 external/google/dense_hash_set create mode 100644 external/google/sparse_hash_map create mode 100644 external/google/sparse_hash_set create mode 100644 external/google/sparsehash/densehashtable.h create mode 100644 external/google/sparsehash/hashtable-common.h create mode 100644 external/google/sparsehash/libc_allocator_with_realloc.h create mode 100644 external/google/sparsehash/os_config.h create mode 100644 external/google/sparsehash/sparseconfig.h create mode 100644 external/google/sparsehash/sparseconfig_win.h create mode 100644 external/google/sparsehash/sparsehashtable.h create mode 100644 external/google/sparsetable create mode 100644 external/google/type_traits.h create mode 100644 src/common/BlockingQueue.cpp create mode 100644 src/common/BlockingQueue.h create mode 100644 src/common/SignalHandler.cpp create mode 100644 src/common/SignalHandler.h create mode 100644 src/cryptonote_core/AccountKVSerialization.h create mode 100644 src/cryptonote_core/BlockIndex.cpp create mode 100644 src/cryptonote_core/BlockIndex.h create mode 100644 src/cryptonote_core/Currency.cpp create mode 100644 src/cryptonote_core/Currency.h create mode 100644 src/cryptonote_core/ITimeProvider.cpp create mode 100644 src/cryptonote_core/ITimeProvider.h create mode 100644 src/cryptonote_core/ITransactionValidator.h create mode 100644 src/cryptonote_core/UpgradeDetector.cpp create mode 100644 src/cryptonote_core/UpgradeDetector.h delete mode 100644 src/cryptonote_core/checkpoints_create.h create mode 100644 src/cryptonote_core/i_miner_handler.h create mode 100644 tests/TestGenerator/TestGenerator.cpp create mode 100644 tests/TestGenerator/TestGenerator.h create mode 100644 tests/core_tests/TestGenerator.h create mode 100644 tests/core_tests/TransactionBuilder.cpp create mode 100644 tests/core_tests/TransactionBuilder.h rename tests/core_tests/{chaingen_tests_list.h => chaingen001.h} (75%) create mode 100644 tests/core_tests/upgrade.cpp create mode 100644 tests/core_tests/upgrade.h create mode 100644 tests/unit_tests/BlockingQueue.cpp create mode 100644 tests/unit_tests/TestUpgradeDetector.cpp create mode 100644 tests/unit_tests/tx_pool.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6dd3638a..243001d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ if(MSVC) # set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Dinline=__inline") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /STACK:10485760") if(STATIC) - foreach(VAR CMAKE_C_FLAGS_DEBUG CMAKE_CXX_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_RELEASE) + foreach(VAR CMAKE_C_FLAGS_DEBUG CMAKE_CXX_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_RELEASE CMAKE_C_FLAGS_RELWITHDEBINFO CMAKE_CXX_FLAGS_RELWITHDEBINFO) string(REPLACE "/MD" "/MT" ${VAR} "${${VAR}}") endforeach() endif() @@ -122,6 +122,7 @@ else() endif() endif() +add_subdirectory(contrib) add_subdirectory(external) add_subdirectory(src) add_subdirectory(tests) diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt index 69836bef..c97cbba0 100644 --- a/ReleaseNotes.txt +++ b/ReleaseNotes.txt @@ -1,3 +1,15 @@ +Release notes 1.0.0 + +- Multi-signatures +- Updated block reward scheme +- Further optimization in daemon RAM consumption +- Faster wallet refresh +- Transaction priority based on tx fee +- Transactions are returned from tx pools after 24 hours +- Dynamic maximum block size limit +- Reduced default transaction fee +- Various network health updates + Release notes 0.8.11 - Increased minimum transaction fee diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt new file mode 100644 index 00000000..7222cce5 --- /dev/null +++ b/contrib/CMakeLists.txt @@ -0,0 +1,7 @@ +file(GLOB_RECURSE EPEE epee/include/*) + +source_group(epee FILES ${EPEE}) + +add_library(epee ${EPEE}) + +set_property(TARGET epee PROPERTY FOLDER "external") diff --git a/contrib/epee/demo/demo_levin_server/stdafx.h b/contrib/epee/demo/demo_levin_server/stdafx.h index f69d5922..cc455843 100644 --- a/contrib/epee/demo/demo_levin_server/stdafx.h +++ b/contrib/epee/demo/demo_levin_server/stdafx.h @@ -35,7 +35,4 @@ #define BOOST_FILESYSTEM_VERSION 3 #define ENABLE_RELEASE_LOGGING -#include "log_opt_defs.h" #include "misc_log_ex.h" - - diff --git a/contrib/epee/include/console_handler.h b/contrib/epee/include/console_handler.h index fcab35aa..a1df7839 100644 --- a/contrib/epee/include/console_handler.h +++ b/contrib/epee/include/console_handler.h @@ -32,6 +32,11 @@ #include #include +#include +#include + +#include "string_tools.h" + namespace epee { class async_stdin_reader @@ -294,7 +299,7 @@ namespace epee bool start_default_console(t_server* ptsrv, t_handler handlr, const std::string& prompt, const std::string& usage = "") { std::shared_ptr console_handler = std::make_shared(); - boost::thread([=](){console_handler->run(ptsrv, handlr, prompt, usage);}).detach(); + std::thread([=](){console_handler->run(ptsrv, handlr, prompt, usage);}).detach(); return true; } @@ -314,46 +319,24 @@ namespace epee bool run_default_console_handler_no_srv_param(t_server* ptsrv, t_handler handlr, const std::string& prompt, const std::string& usage = "") { async_console_handler console_handler; - return console_handler.run(ptsrv, boost::bind(no_srv_param_adapter, _1, _2, handlr), prompt, usage); + return console_handler.run(ptsrv, std::bind(no_srv_param_adapter, std::placeholders::_1, std::placeholders::_2, handlr), prompt, usage); } template bool start_default_console_handler_no_srv_param(t_server* ptsrv, t_handler handlr, const std::string& prompt, const std::string& usage = "") { - boost::thread( boost::bind(run_default_console_handler_no_srv_param, ptsrv, handlr, prompt, usage) ); + std::thread( std::bind(run_default_console_handler_no_srv_param, ptsrv, handlr, prompt, usage) ); return true; } - /*template - bool f(int i, a l) - { - return true; - }*/ - /* - template - bool default_console_handler2(chain_handler ch_handler, const std::string usage) - */ - - - /*template - bool start_default_console2(t_handler handlr, const std::string& usage = "") - { - //std::string usage_local = usage; - boost::thread( boost::bind(default_console_handler2, handlr, usage) ); - //boost::function p__ = boost::bind(f, 1, handlr); - //boost::function p__ = boost::bind(default_console_handler2, handlr, usage); - //boost::thread tr(p__); - return true; - }*/ - /************************************************************************/ /* */ /************************************************************************/ class console_handlers_binder { - typedef boost::function &)> console_command_handler; + typedef std::function &)> console_command_handler; typedef std::map > command_handlers_map; - std::unique_ptr m_console_thread; + std::unique_ptr m_console_thread; command_handlers_map m_command_handlers; async_console_handler m_console_handler; public: @@ -396,16 +379,9 @@ namespace epee return process_command_vec(cmd_v); } - /*template - bool start_handling(t_srv& srv, const std::string& usage_string = "") - { - start_default_console_handler_no_srv_param(&srv, boost::bind(&console_handlers_binder::process_command_str, this, _1)); - return true; - }*/ - bool start_handling(const std::string& prompt, const std::string& usage_string = "") { - m_console_thread.reset(new boost::thread(boost::bind(&console_handlers_binder::run_handling, this, prompt, usage_string))); + m_console_thread.reset(new std::thread(std::bind(&console_handlers_binder::run_handling, this, prompt, usage_string))); m_console_thread->detach(); return true; } @@ -417,14 +393,8 @@ namespace epee bool run_handling(const std::string& prompt, const std::string& usage_string) { - return m_console_handler.run(boost::bind(&console_handlers_binder::process_command_str, this, _1), prompt, usage_string); + return m_console_handler.run(std::bind(&console_handlers_binder::process_command_str, this, std::placeholders::_1), prompt, usage_string); } - - /*template - bool run_handling(t_srv& srv, const std::string& usage_string) - { - return run_default_console_handler_no_srv_param(&srv, boost::bind(&console_handlers_binder::process_command_str, this, _1), usage_string); - }*/ }; /* work around because of broken boost bind */ @@ -438,13 +408,14 @@ namespace epee public: bool start_handling(t_server* psrv, const std::string& prompt, const std::string& usage_string = "") { - boost::thread(boost::bind(&srv_console_handlers_binder::run_handling, this, psrv, prompt, usage_string)).detach(); + std::thread(std::bind(&srv_console_handlers_binder::run_handling, this, psrv, prompt, usage_string)).detach(); return true; } bool run_handling(t_server* psrv, const std::string& prompt, const std::string& usage_string) { - return m_console_handler.run(psrv, boost::bind(&srv_console_handlers_binder::process_command_str, this, _1, _2), prompt, usage_string); + return m_console_handler.run(psrv, std::bind(&srv_console_handlers_binder::process_command_str, this, + std::placeholders::_1, std::placeholders::_2), prompt, usage_string); } void stop_handling() diff --git a/contrib/epee/include/file_io_utils.h b/contrib/epee/include/file_io_utils.h index 7e852183..01d70db3 100644 --- a/contrib/epee/include/file_io_utils.h +++ b/contrib/epee/include/file_io_utils.h @@ -28,10 +28,7 @@ #ifndef _FILE_IO_UTILS_H_ #define _FILE_IO_UTILS_H_ - -//#include -//#include - +#include #include #include @@ -75,7 +72,7 @@ namespace file_io_utils #ifdef BOOST_LEXICAL_CAST_INCLUDED inline - bool get_not_used_filename(const std::string& folder, OUT std::string& result_name) + bool get_not_used_filename(const std::string& folder, std::string& result_name) { DWORD folder_attr = ::GetFileAttributesA(folder.c_str()); if(folder_attr == INVALID_FILE_ATTRIBUTES) @@ -302,7 +299,7 @@ namespace file_io_utils } */ inline - bool get_file_time(const std::string& path_to_file, OUT time_t& ft) + bool get_file_time(const std::string& path_to_file, time_t& ft) { boost::system::error_code ec; ft = boost::filesystem::last_write_time(boost::filesystem::path(path_to_file), ec); @@ -408,7 +405,7 @@ namespace file_io_utils } */ #ifdef WINDOWS_PLATFORM - inline bool get_folder_content(const std::string& path, std::list& OUT target_list) + inline bool get_folder_content(const std::string& path, std::list& target_list) { WIN32_FIND_DATAA find_data = {0}; HANDLE hfind = ::FindFirstFileA((path + "\\*.*").c_str(), &find_data); @@ -426,7 +423,7 @@ namespace file_io_utils return true; } #endif - inline bool get_folder_content(const std::string& path, std::list& OUT target_list, bool only_files = false) + inline bool get_folder_content(const std::string& path, std::list& target_list, bool only_files = false) { try { diff --git a/contrib/epee/include/math_helper.h b/contrib/epee/include/math_helper.h index 349d6d82..11faa976 100644 --- a/contrib/epee/include/math_helper.h +++ b/contrib/epee/include/math_helper.h @@ -37,6 +37,7 @@ #include #include "misc_os_dependent.h" +#include "pragma_comp_defs.h" namespace epee { diff --git a/contrib/epee/include/misc_log_ex.cpp b/contrib/epee/include/misc_log_ex.cpp new file mode 100644 index 00000000..0c0b441b --- /dev/null +++ b/contrib/epee/include/misc_log_ex.cpp @@ -0,0 +1,1029 @@ +// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of the Andrey N. Sabelnikov nor the +// names of its contributors may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#include "misc_log_ex.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(WIN32) +#include +#else +#include +#endif + +#include "static_initializer.h" +#include "string_tools.h" +#include "time_helper.h" +#include "misc_os_dependent.h" + +#include "syncobj.h" + + +#define LOG_LEVEL_SILENT -1 +#define LOG_LEVEL_0 0 +#define LOG_LEVEL_1 1 +#define LOG_LEVEL_2 2 +#define LOG_LEVEL_3 3 +#define LOG_LEVEL_4 4 +#define LOG_LEVEL_MIN LOG_LEVEL_SILENT +#define LOG_LEVEL_MAX LOG_LEVEL_4 + + +#define LOGGER_NULL 0 +#define LOGGER_FILE 1 +#define LOGGER_DEBUGGER 2 +#define LOGGER_CONSOLE 3 +#define LOGGER_DUMP 4 + + +#ifndef LOCAL_ASSERT +#include +#if (defined _MSC_VER) +#define LOCAL_ASSERT(expr) {if(epee::debug::get_set_enable_assert()){_ASSERTE(expr);}} +#else +#define LOCAL_ASSERT(expr) +#endif + +#endif + +namespace epee { +namespace log_space { + //---------------------------------------------------------------------------- + bool is_stdout_a_tty() + { + static std::atomic initialized(false); + static std::atomic is_a_tty(false); + + if (!initialized.load(std::memory_order_acquire)) + { +#if defined(WIN32) + is_a_tty.store(0 != _isatty(_fileno(stdout)), std::memory_order_relaxed); +#else + is_a_tty.store(0 != isatty(fileno(stdout)), std::memory_order_relaxed); +#endif + initialized.store(true, std::memory_order_release); + } + + return is_a_tty.load(std::memory_order_relaxed); + } + //---------------------------------------------------------------------------- + void set_console_color(int color, bool bright) + { + if (!is_stdout_a_tty()) + return; + + switch(color) + { + case console_color_default: + { +#ifdef WIN32 + HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(h_stdout, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE| (bright ? FOREGROUND_INTENSITY:0)); +#else + if(bright) + std::cout << "\033[1;37m"; + else + std::cout << "\033[0m"; +#endif + } + break; + case console_color_white: + { +#ifdef WIN32 + HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(h_stdout, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | (bright ? FOREGROUND_INTENSITY:0)); +#else + if(bright) + std::cout << "\033[1;37m"; + else + std::cout << "\033[0;37m"; +#endif + } + break; + case console_color_red: + { +#ifdef WIN32 + HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(h_stdout, FOREGROUND_RED | (bright ? FOREGROUND_INTENSITY:0)); +#else + if(bright) + std::cout << "\033[1;31m"; + else + std::cout << "\033[0;31m"; +#endif + } + break; + case console_color_green: + { +#ifdef WIN32 + HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(h_stdout, FOREGROUND_GREEN | (bright ? FOREGROUND_INTENSITY:0)); +#else + if(bright) + std::cout << "\033[1;32m"; + else + std::cout << "\033[0;32m"; +#endif + } + break; + + case console_color_blue: + { +#ifdef WIN32 + HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(h_stdout, FOREGROUND_BLUE | FOREGROUND_INTENSITY);//(bright ? FOREGROUND_INTENSITY:0)); +#else + if(bright) + std::cout << "\033[1;34m"; + else + std::cout << "\033[0;34m"; +#endif + } + break; + + case console_color_cyan: + { +#ifdef WIN32 + HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(h_stdout, FOREGROUND_GREEN | FOREGROUND_BLUE | (bright ? FOREGROUND_INTENSITY:0)); +#else + if(bright) + std::cout << "\033[1;36m"; + else + std::cout << "\033[0;36m"; +#endif + } + break; + + case console_color_magenta: + { +#ifdef WIN32 + HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(h_stdout, FOREGROUND_BLUE | FOREGROUND_RED | (bright ? FOREGROUND_INTENSITY:0)); +#else + if(bright) + std::cout << "\033[1;35m"; + else + std::cout << "\033[0;35m"; +#endif + } + break; + + case console_color_yellow: + { +#ifdef WIN32 + HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(h_stdout, FOREGROUND_RED | FOREGROUND_GREEN | (bright ? FOREGROUND_INTENSITY:0)); +#else + if(bright) + std::cout << "\033[1;33m"; + else + std::cout << "\033[0;33m"; +#endif + } + break; + + } + } + //---------------------------------------------------------------------------- + void reset_console_color() { + if (!is_stdout_a_tty()) + return; + +#ifdef WIN32 + HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(h_stdout, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); +#else + std::cout << "\033[0m"; + std::cout.flush(); +#endif + } + //---------------------------------------------------------------------------- + bool rotate_log_file(const char* pfile_path) + { +#ifdef _MSC_VER + if(!pfile_path) + return false; + + std::string file_path = pfile_path; + std::string::size_type a = file_path .rfind('.'); + if ( a != std::string::npos ) + file_path .erase( a, file_path .size()); + + ::DeleteFileA( (file_path + ".0").c_str() ); + ::MoveFileA( (file_path + ".log").c_str(), (file_path + ".0").c_str() ); +#else + return false;//not implemented yet +#endif + return true; + } + //---------------------------------------------------------------------------- +#ifdef _MSC_VER + bool debug_output_stream::out_buffer( const char* buffer, int buffer_len , int log_level, int color, const char* plog_name/* = NULL*/) + { + for ( int i = 0; i < buffer_len; i = i + max_dbg_str_len ) + { + std::string s( buffer + i, buffer_len- i < max_dbg_str_len ? + buffer_len - i : max_dbg_str_len ); + + ::OutputDebugStringA( s.c_str() ); + } + return true; + } +#endif + //---------------------------------------------------------------------------- + console_output_stream::console_output_stream() + { +#ifdef _MSC_VER + + if(!::GetStdHandle(STD_OUTPUT_HANDLE)) + m_have_to_kill_console = true; + else + m_have_to_kill_console = false; + + ::AllocConsole(); +#endif + } + //---------------------------------------------------------------------------- + console_output_stream::~console_output_stream() + { +#ifdef _MSC_VER + if(m_have_to_kill_console) + ::FreeConsole(); +#endif + } + //---------------------------------------------------------------------------- + bool console_output_stream::out_buffer( const char* buffer, int buffer_len , int log_level, int color, const char* plog_name/* = NULL*/) + { + if(plog_name) + return true; //skip alternative logs from console + + set_console_color(color, log_level < 1); + +#ifdef _MSC_VER + const char* ptarget_buf = NULL; + char* pallocated_buf = NULL; + + // + int i = 0; + for(; i < buffer_len; i++) + if(buffer[i] == '\a') break; + if(i == buffer_len) + ptarget_buf = buffer; + else + { + pallocated_buf = new char[buffer_len]; + ptarget_buf = pallocated_buf; + for(i = 0; i < buffer_len; i++) + { + if(buffer[i] == '\a') + pallocated_buf[i] = '^'; + else + pallocated_buf[i] = buffer[i]; + } + } + + //uint32_t b = 0; + //::WriteConsoleA(::GetStdHandle(STD_OUTPUT_HANDLE), ptarget_buf, buffer_len, (DWORD*)&b, 0); + std::cout << ptarget_buf; + if(pallocated_buf) delete [] pallocated_buf; +#else + std::string buf(buffer, buffer_len); + for(size_t i = 0; i!= buf.size(); i++) + { + if(buf[i] == 7 || buf[i] == -107) + buf[i] = '^'; + } + + std::cout << buf; +#endif + reset_console_color(); + return true; + } + //---------------------------------------------------------------------------- + file_output_stream::file_output_stream(std::string default_log_file_name, std::string log_path) + { + m_default_log_filename = default_log_file_name; + m_max_logfile_size = 0; + m_default_log_path = log_path; + m_pdefault_file_stream = add_new_stream_and_open(default_log_file_name.c_str()); + } + //---------------------------------------------------------------------------- + file_output_stream::~file_output_stream() + { + for(named_log_streams::iterator it = m_log_file_names.begin(); it!=m_log_file_names.end(); it++) + { + if ( it->second->is_open() ) + { + it->second->flush(); + it->second->close(); + } + delete it->second; + } + } + //---------------------------------------------------------------------------- + std::ofstream* file_output_stream::add_new_stream_and_open(const char* pstream_name) + { + //log_space::rotate_log_file((m_default_log_path + "\\" + pstream_name).c_str()); + + std::ofstream* pstream = (m_log_file_names[pstream_name] = new std::ofstream); + std::string target_path = m_default_log_path + "/" + pstream_name; + pstream->open( target_path.c_str(), std::ios_base::out | std::ios::app /*ios_base::trunc */); + if(pstream->fail()) + return NULL; + return pstream; + } + //---------------------------------------------------------------------------- + bool file_output_stream::set_max_logfile_size(uint64_t max_size) + { + m_max_logfile_size = max_size; + return true; + } + //---------------------------------------------------------------------------- + bool file_output_stream::set_log_rotate_cmd(const std::string& cmd) + { + m_log_rotate_cmd = cmd; + return true; + } + //---------------------------------------------------------------------------- + bool file_output_stream::out_buffer(const char* buffer, int buffer_len, int log_level, int color, const char* plog_name/* = NULL*/) + { + std::ofstream* m_target_file_stream = m_pdefault_file_stream; + if(plog_name) + { //find named stream + named_log_streams::iterator it = m_log_file_names.find(plog_name); + if(it == m_log_file_names.end()) + m_target_file_stream = add_new_stream_and_open(plog_name); + else + m_target_file_stream = it->second; + } + if(!m_target_file_stream || !m_target_file_stream->is_open()) + return false;//TODO: add assert here + + m_target_file_stream->write(buffer, buffer_len ); + m_target_file_stream->flush(); + + if(m_max_logfile_size) + { + std::ofstream::pos_type pt = m_target_file_stream->tellp(); + uint64_t current_sz = pt; + if(current_sz > m_max_logfile_size) + { + std::cout << "current_sz= " << current_sz << " m_max_logfile_size= " << m_max_logfile_size << std::endl; + std::string log_file_name; + if(!plog_name) + log_file_name = m_default_log_filename; + else + log_file_name = plog_name; + + m_target_file_stream->close(); + std::string new_log_file_name = log_file_name; + + time_t tm = 0; + time(&tm); + + int err_count = 0; + boost::system::error_code ec; + do + { + new_log_file_name = string_tools::cut_off_extension(log_file_name); + if(err_count) + new_log_file_name += misc_utils::get_time_str_v2(tm) + "(" + boost::lexical_cast(err_count) + ")" + ".log"; + else + new_log_file_name += misc_utils::get_time_str_v2(tm) + ".log"; + + err_count++; + }while(boost::filesystem::exists(m_default_log_path + "/" + new_log_file_name, ec)); + + std::string new_log_file_path = m_default_log_path + "/" + new_log_file_name; + boost::filesystem::rename(m_default_log_path + "/" + log_file_name, new_log_file_path, ec); + if(ec) + { + std::cout << "Filed to rename, ec = " << ec.message() << std::endl; + } + + if(m_log_rotate_cmd.size()) + { + + std::string m_log_rotate_cmd_local_copy = m_log_rotate_cmd; + //boost::replace_all(m_log_rotate_cmd, "[*SOURCE*]", new_log_file_path); + boost::replace_all(m_log_rotate_cmd_local_copy, "[*TARGET*]", new_log_file_path); + + misc_utils::call_sys_cmd(m_log_rotate_cmd_local_copy); + } + + m_target_file_stream->open( (m_default_log_path + "/" + log_file_name).c_str(), std::ios_base::out | std::ios::app /*ios_base::trunc */); + if(m_target_file_stream->fail()) + return false; + } + } + + return true; + } + //---------------------------------------------------------------------------- + log_stream_splitter::~log_stream_splitter() + { + //free pointers + std::for_each(m_log_streams.begin(), m_log_streams.end(), delete_ptr()); + } + //---------------------------------------------------------------------------- + bool log_stream_splitter::set_max_logfile_size(uint64_t max_size) + { + for(streams_container::iterator it = m_log_streams.begin(); it!=m_log_streams.end();it++) + it->first->set_max_logfile_size(max_size); + return true; + } + //---------------------------------------------------------------------------- + bool log_stream_splitter::set_log_rotate_cmd(const std::string& cmd) + { + for(streams_container::iterator it = m_log_streams.begin(); it!=m_log_streams.end();it++) + it->first->set_log_rotate_cmd(cmd); + return true; + } + //---------------------------------------------------------------------------- + bool log_stream_splitter::do_log_message(const std::string& rlog_mes, int log_level, int color, const char* plog_name/* = NULL*/) + { + std::string str_mess = rlog_mes; + size_t str_len = str_mess.size(); + const char* pstr = str_mess.c_str(); + for(streams_container::iterator it = m_log_streams.begin(); it!=m_log_streams.end();it++) + if(it->second >= log_level) + it->first->out_buffer(pstr, (int)str_len, log_level, color, plog_name); + return true; + } + //---------------------------------------------------------------------------- + bool log_stream_splitter::add_logger(int type, const char* pdefault_file_name, const char* pdefault_log_folder, int log_level_limit/* = LOG_LEVEL_4*/) + { + ibase_log_stream* ls = NULL; + + switch( type ) + { + case LOGGER_FILE: + ls = new file_output_stream( pdefault_file_name, pdefault_log_folder ); + break; + + case LOGGER_DEBUGGER: +#ifdef _MSC_VER + ls = new debug_output_stream( ); +#else + return false;//not implemented yet +#endif + break; + case LOGGER_CONSOLE: + ls = new console_output_stream( ); + break; + } + + if ( ls ) { + m_log_streams.push_back(streams_container::value_type(ls, log_level_limit)); + return true; + } + return ls ? true:false; + } + //---------------------------------------------------------------------------- + bool log_stream_splitter::add_logger(ibase_log_stream* pstream, int log_level_limit/* = LOG_LEVEL_4*/) + { + m_log_streams.push_back(streams_container::value_type(pstream, log_level_limit) ); + return true; + } + //---------------------------------------------------------------------------- + bool log_stream_splitter::remove_logger(int type) + { + streams_container::iterator it = m_log_streams.begin(); + for(;it!=m_log_streams.end(); it++) + { + if(it->first->get_type() == type) + { + delete it->first; + m_log_streams.erase(it); + return true; + } + } + return false; + } + //---------------------------------------------------------------------------- + std::string get_daytime_string2() + { + boost::posix_time::ptime p = boost::posix_time::microsec_clock::local_time(); + return misc_utils::get_time_str_v3(p); + } + //---------------------------------------------------------------------------- + std::string get_day_time_string() + { + return get_daytime_string2(); + //time_t tm = 0; + //time(&tm); + //return misc_utils::get_time_str(tm); + } + //---------------------------------------------------------------------------- + std::string get_time_string() + { + return get_daytime_string2(); + } + //---------------------------------------------------------------------------- +#ifdef _MSC_VER + std::string get_time_string_adv(SYSTEMTIME* pst/* = NULL*/) + { + SYSTEMTIME st = {0}; + if(!pst) + { + pst = &st; + GetSystemTime(&st); + } + std::stringstream str_str; + str_str.fill('0'); + str_str << std::setw(2) << pst->wHour << "_" + << std::setw(2) << pst->wMinute << "_" + << std::setw(2) << pst->wSecond << "_" + << std::setw(3) << pst->wMilliseconds; + return str_str.str(); + } +#endif + //---------------------------------------------------------------------------- + logger::logger() + { + CRITICAL_REGION_BEGIN(m_critical_sec); + init(); + CRITICAL_REGION_END(); + } + //---------------------------------------------------------------------------- + bool logger::set_max_logfile_size(uint64_t max_size) + { + CRITICAL_REGION_BEGIN(m_critical_sec); + m_log_target.set_max_logfile_size(max_size); + CRITICAL_REGION_END(); + return true; + } + //---------------------------------------------------------------------------- + bool logger::set_log_rotate_cmd(const std::string& cmd) + { + CRITICAL_REGION_BEGIN(m_critical_sec); + m_log_target.set_log_rotate_cmd(cmd); + CRITICAL_REGION_END(); + return true; + } + //---------------------------------------------------------------------------- + bool logger::take_away_journal(std::list& journal) + { + CRITICAL_REGION_BEGIN(m_critical_sec); + m_journal.swap(journal); + CRITICAL_REGION_END(); + return true; + } + //---------------------------------------------------------------------------- + bool logger::do_log_message(const std::string& rlog_mes, int log_level, int color, bool add_to_journal/* = false*/, const char* plog_name/* = NULL*/) + { + CRITICAL_REGION_BEGIN(m_critical_sec); + m_log_target.do_log_message(rlog_mes, log_level, color, plog_name); + if(add_to_journal) + m_journal.push_back(rlog_mes); + + return true; + CRITICAL_REGION_END(); + } + //---------------------------------------------------------------------------- + bool logger::add_logger( int type, const char* pdefault_file_name, const char* pdefault_log_folder, int log_level_limit/* = LOG_LEVEL_4*/) + { + CRITICAL_REGION_BEGIN(m_critical_sec); + return m_log_target.add_logger( type, pdefault_file_name, pdefault_log_folder, log_level_limit); + CRITICAL_REGION_END(); + } + //---------------------------------------------------------------------------- + bool logger::add_logger( ibase_log_stream* pstream, int log_level_limit/* = LOG_LEVEL_4*/) + { + CRITICAL_REGION_BEGIN(m_critical_sec); + return m_log_target.add_logger(pstream, log_level_limit); + CRITICAL_REGION_END(); + } + //---------------------------------------------------------------------------- + bool logger::remove_logger(int type) + { + CRITICAL_REGION_BEGIN(m_critical_sec); + return m_log_target.remove_logger(type); + CRITICAL_REGION_END(); + } + //---------------------------------------------------------------------------- + bool logger::set_thread_prefix(const std::string& prefix) + { + CRITICAL_REGION_BEGIN(m_critical_sec); + m_thr_prefix_strings[misc_utils::get_thread_string_id()] = prefix; + CRITICAL_REGION_END(); + return true; + } + //---------------------------------------------------------------------------- + bool logger::init() + { + m_process_name = string_tools::get_current_module_name(); + + init_log_path_by_default(); + + //init default set of loggers + init_default_loggers(); + + std::stringstream ss; + ss << get_time_string() << " Init logging. Level=" << get_set_log_detalisation_level() + << " Log path=" << m_default_log_folder << std::endl; + this->do_log_message(ss.str(), console_color_white, LOG_LEVEL_0); + return true; + } + //---------------------------------------------------------------------------- + bool logger::init_default_loggers() + { + return true; + } + //---------------------------------------------------------------------------- + bool logger::init_log_path_by_default() + { + //load process name + m_default_log_folder = string_tools::get_current_module_folder(); + + m_default_log_file = m_process_name; + std::string::size_type a = m_default_log_file.rfind('.'); + if ( a != std::string::npos ) + m_default_log_file.erase( a, m_default_log_file.size()); + m_default_log_file += ".log"; + + return true; + } + //---------------------------------------------------------------------------- + int log_singletone::get_log_detalisation_level() + { + get_or_create_instance();//to initialize logger, if it not initialized + return get_set_log_detalisation_level(); + } + //---------------------------------------------------------------------------- + bool log_singletone::is_filter_error(int error_code) + { + return false; + } + //---------------------------------------------------------------------------- + bool log_singletone::do_log_message(const std::string& rlog_mes, int log_level, int color, bool keep_in_journal, const char* plog_name/* = NULL*/) + { + logger* plogger = get_or_create_instance(); + bool res = false; + if(plogger) + res = plogger->do_log_message(rlog_mes, log_level, color, keep_in_journal, plog_name); + else + { //globally uninitialized, create new logger for each call of do_log_message() and then delete it + plogger = new logger(); + //TODO: some extra initialization + res = plogger->do_log_message(rlog_mes, log_level, color, keep_in_journal, plog_name); + delete plogger; + plogger = NULL; + } + return res; + } + //---------------------------------------------------------------------------- + bool log_singletone::take_away_journal(std::list& journal) + { + logger* plogger = get_or_create_instance(); + bool res = false; + if(plogger) + res = plogger->take_away_journal(journal); + + return res; + } + //---------------------------------------------------------------------------- + bool log_singletone::set_max_logfile_size(uint64_t file_size) + { + logger* plogger = get_or_create_instance(); + if(!plogger) return false; + return plogger->set_max_logfile_size(file_size); + } + //---------------------------------------------------------------------------- + bool log_singletone::set_log_rotate_cmd(const std::string& cmd) + { + logger* plogger = get_or_create_instance(); + if(!plogger) return false; + return plogger->set_log_rotate_cmd(cmd); + } + //---------------------------------------------------------------------------- + bool log_singletone::add_logger( int type, const char* pdefault_file_name, const char* pdefault_log_folder, int log_level_limit/* = LOG_LEVEL_4*/) + { + logger* plogger = get_or_create_instance(); + if(!plogger) return false; + return plogger->add_logger(type, pdefault_file_name, pdefault_log_folder, log_level_limit); + } + //---------------------------------------------------------------------------- + std::string log_singletone::get_default_log_file() + { + logger* plogger = get_or_create_instance(); + if(plogger) + return plogger->get_default_log_file(); + + return ""; + } + //---------------------------------------------------------------------------- + std::string log_singletone::get_default_log_folder() + { + logger* plogger = get_or_create_instance(); + if(plogger) + return plogger->get_default_log_folder(); + + return ""; + } + //---------------------------------------------------------------------------- + bool log_singletone::add_logger(ibase_log_stream* pstream, int log_level_limit/* = LOG_LEVEL_4*/) + { + logger* plogger = get_or_create_instance(); + if(!plogger) return false; + return plogger->add_logger(pstream, log_level_limit); + } + //---------------------------------------------------------------------------- + bool log_singletone::remove_logger(int type) + { + logger* plogger = get_or_create_instance(); + if(!plogger) return false; + return plogger->remove_logger(type); + } + //---------------------------------------------------------------------------- +PUSH_WARNINGS +DISABLE_GCC_WARNING(maybe-uninitialized) + int log_singletone::get_set_log_detalisation_level(bool is_need_set/* = false*/, int log_level_to_set/* = LOG_LEVEL_1*/) + { + static int log_detalisation_level = LOG_LEVEL_1; + if(is_need_set) + log_detalisation_level = log_level_to_set; + return log_detalisation_level; + } +POP_WARNINGS + //---------------------------------------------------------------------------- + int log_singletone::get_set_time_level(bool is_need_set/* = false*/, int time_log_level/* = LOG_LEVEL_0*/) + { + static int val_time_log_level = LOG_LEVEL_0; + if(is_need_set) + val_time_log_level = time_log_level; + + return val_time_log_level; + } + //---------------------------------------------------------------------------- + int log_singletone::get_set_process_level(bool is_need_set/* = false*/, int process_log_level/* = LOG_LEVEL_0*/) + { + static int val_process_log_level = LOG_LEVEL_0; + if(is_need_set) + val_process_log_level = process_log_level; + + return val_process_log_level; + } + //---------------------------------------------------------------------------- + bool log_singletone::get_set_need_thread_id(bool is_need_set/* = false*/, bool is_need_val/* = false*/) + { + static bool is_need = false; + if(is_need_set) + is_need = is_need_val; + + return is_need; + } + //---------------------------------------------------------------------------- + bool log_singletone::get_set_need_proc_name(bool is_need_set/* = false*/, bool is_need_val/* = false*/) + { + static bool is_need = true; + if(is_need_set) + is_need = is_need_val; + + return is_need; + } + //---------------------------------------------------------------------------- + uint64_t log_singletone::get_set_err_count(bool is_need_set/* = false*/, uint64_t err_val/* = false*/) + { + static uint64_t err_count = 0; + if(is_need_set) + err_count = err_val; + + return err_count; + } + //---------------------------------------------------------------------------- +#ifdef _MSC_VER + void log_singletone::SetThreadName( DWORD dwThreadID, const char* threadName) + { +#define MS_VC_EXCEPTION 0x406D1388 + +#pragma pack(push,8) + typedef struct tagTHREADNAME_INFO + { + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to name (in user addr space). + DWORD dwThreadID; // Thread ID (-1=caller thread). + DWORD dwFlags; // Reserved for future use, must be zero. + } THREADNAME_INFO; +#pragma pack(pop) + + Sleep(10); + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = (char*)threadName; + info.dwThreadID = dwThreadID; + info.dwFlags = 0; + + __try + { + RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info ); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + } + } +#endif + //---------------------------------------------------------------------------- + bool log_singletone::set_thread_log_prefix(const std::string& prefix) + { +#ifdef _MSC_VER + SetThreadName(-1, prefix.c_str()); +#endif + + logger* plogger = get_or_create_instance(); + if(!plogger) return false; + return plogger->set_thread_prefix(prefix); + } + //---------------------------------------------------------------------------- + std::string log_singletone::get_prefix_entry() + { + std::stringstream str_prefix; + //write time entry + if ( get_set_time_level() <= get_set_log_detalisation_level() ) + str_prefix << get_day_time_string() << " "; + + //write process info + logger* plogger = get_or_create_instance(); + //bool res = false; + if(!plogger) + { //globally uninitialized, create new logger for each call of get_prefix_entry() and then delete it + plogger = new logger(); + } + + //if ( get_set_need_proc_name() && get_set_process_level() <= get_set_log_detalisation_level() ) + // str_prefix << "[" << plogger->m_process_name << " (id=" << GetCurrentProcessId() << ")] "; +//#ifdef _MSC_VER_EX + if ( get_set_need_thread_id() /*&& get_set_tid_level() <= get_set_log_detalisation_level()*/ ) + str_prefix << "tid:" << misc_utils::get_thread_string_id() << " "; +//#endif + + if(plogger->m_thr_prefix_strings.size()) + { + CRITICAL_REGION_LOCAL(plogger->m_critical_sec); + std::string thr_str = misc_utils::get_thread_string_id(); + std::map::iterator it = plogger->m_thr_prefix_strings.find(thr_str); + if(it!=plogger->m_thr_prefix_strings.end()) + { + str_prefix << it->second; + } + } + + if(get_set_is_uninitialized()) + delete plogger; + + return str_prefix.str(); + } + //---------------------------------------------------------------------------- + bool log_singletone::init() + { + return true;/*do nothing here*/ + } + //---------------------------------------------------------------------------- + bool log_singletone::un_init() + { + //delete object + logger* plogger = get_set_instance_internal(); + if(plogger) delete plogger; + //set uninitialized + get_set_is_uninitialized(true, true); + get_set_instance_internal(true, NULL); + return true; + } + //---------------------------------------------------------------------------- + logger* log_singletone::get_or_create_instance() + { + logger* plogger = get_set_instance_internal(); + if(!plogger) + if(!get_set_is_uninitialized()) + get_set_instance_internal(true, plogger = new logger); + + return plogger; + } + //---------------------------------------------------------------------------- + logger* log_singletone::get_set_instance_internal(bool is_need_set/* = false*/, logger* pnew_logger_val/* = NULL*/) + { + static logger* val_plogger = NULL; + + if(is_need_set) + val_plogger = pnew_logger_val; + + return val_plogger; + } + //---------------------------------------------------------------------------- + bool log_singletone::get_set_is_uninitialized(bool is_need_set/* = false*/, bool is_uninitialized/* = false*/) + { + static bool val_is_uninitialized = false; + + if(is_need_set) + val_is_uninitialized = is_uninitialized; + + return val_is_uninitialized; + } + //---------------------------------------------------------------------------- + log_frame::log_frame(const std::string& name, int dlevel/* = LOG_LEVEL_2*/, const char* plog_name/* = NULL*/) + { +#ifdef _MSC_VER + int lasterr=::GetLastError(); +#endif + m_plog_name = plog_name; + if ( dlevel <= log_singletone::get_log_detalisation_level() ) + { + m_name = name; + std::stringstream ss; + ss << log_space::log_singletone::get_prefix_entry() << "-->>" << m_name << std::endl; + log_singletone::do_log_message(ss.str(), dlevel, console_color_default, false, m_plog_name); + } + m_level = dlevel; +#ifdef _MSC_VER + ::SetLastError(lasterr); +#endif + } + //---------------------------------------------------------------------------- + log_frame::~log_frame() + { +#ifdef _MSC_VER + int lasterr=::GetLastError(); +#endif + + if (m_level <= log_singletone::get_log_detalisation_level() ) + { + std::stringstream ss; + ss << log_space::log_singletone::get_prefix_entry() << "<<--" << m_name << std::endl; + log_singletone::do_log_message(ss.str(), m_level, console_color_default, false,m_plog_name); + } +#ifdef _MSC_VER + ::SetLastError(lasterr); +#endif + } + //---------------------------------------------------------------------------- + std::string get_win32_err_descr(int err_no) + { +#ifdef _MSC_VER + LPVOID lpMsgBuf; + + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + err_no, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (char*) &lpMsgBuf, + 0, NULL ); + + std::string fix_sys_message = "(null)"; + if(lpMsgBuf) fix_sys_message = (char*)lpMsgBuf; + std::string::size_type a; + if ( (a = fix_sys_message.rfind( '\n' )) != std::string::npos ) + fix_sys_message.erase(a); + if ( (a = fix_sys_message.rfind( '\r' )) != std::string::npos ) + fix_sys_message.erase(a); + + LocalFree(lpMsgBuf); + return fix_sys_message; +#else + return "Not implemented yet"; +#endif + } + //---------------------------------------------------------------------------- + bool getwin32_err_text(std::stringstream& ref_message, int error_no) + { + ref_message << "win32 error:" << get_win32_err_descr(error_no); + return true; + } +} +} diff --git a/contrib/epee/include/misc_log_ex.h b/contrib/epee/include/misc_log_ex.h index 44f50afa..918bfcf2 100644 --- a/contrib/epee/include/misc_log_ex.h +++ b/contrib/epee/include/misc_log_ex.h @@ -24,38 +24,27 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // +#pragma once #ifndef _MISC_LOG_EX_H_ #define _MISC_LOG_EX_H_ -//#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #if defined(WIN32) -#include -#else -#include +#if !defined(WIN32_LEAN_AND_MEAN) +#define WIN32_LEAN_AND_MEAN +#endif #endif -#include "static_initializer.h" -#include "string_tools.h" -#include "time_helper.h" -#include "misc_os_dependent.h" +#include +#include +#include +#include +#include +#include "misc_os_dependent.h" +#include "static_initializer.h" #include "syncobj.h" +#include "warnings.h" #define LOG_LEVEL_SILENT -1 @@ -121,50 +110,15 @@ namespace log_space struct ibase_log_stream { - ibase_log_stream(){} - virtual ~ibase_log_stream(){} - virtual bool out_buffer( const char* buffer, int buffer_len , int log_level, int color, const char* plog_name = NULL)=0; - virtual int get_type(){return 0;} + ibase_log_stream() {} + virtual ~ibase_log_stream() {} + virtual bool out_buffer(const char* buffer, int buffer_len , int log_level, int color, const char* plog_name = NULL)=0; + virtual int get_type() const { return 0; } - virtual bool set_max_logfile_size(uint64_t max_size){return true;}; - virtual bool set_log_rotate_cmd(const std::string& cmd){return true;}; + virtual bool set_max_logfile_size(uint64_t max_size) { return true; } + virtual bool set_log_rotate_cmd(const std::string& cmd) { return true; } }; - /************************************************************************/ - /* */ - /************************************************************************/ - /*struct ibase_log_value - { - public: - virtual void debug_out( std::stringstream* p_stream)const = 0; - };*/ - - /************************************************************************/ - /* */ - /************************************************************************/ - /*class log_message: public std::stringstream - { - public: - log_message(const log_message& lm): std::stringstream(), std::stringstream::basic_ios() - {} - log_message(){} - - template - log_message& operator<< (T t) - { - std::stringstream* pstrstr = this; - (*pstrstr) << t; - - return *this; - } - }; - inline - log_space::log_message& operator<<(log_space::log_message& sstream, const ibase_log_value& log_val) - { - log_val.debug_out(&sstream); - return sstream; - } - */ /************************************************************************/ /* */ /************************************************************************/ @@ -180,176 +134,21 @@ namespace log_space /************************************************************************/ /* */ /************************************************************************/ + + bool is_stdout_a_tty(); + void set_console_color(int color, bool bright); + void reset_console_color(); + bool rotate_log_file(const char* pfile_path); + //------------------------------------------------------------------------ #define max_dbg_str_len 80 #ifdef _MSC_VER class debug_output_stream: public ibase_log_stream { - virtual bool out_buffer( const char* buffer, int buffer_len , int log_level, int color, const char* plog_name = NULL) - { - for ( int i = 0; i < buffer_len; i = i + max_dbg_str_len ) - { - std::string s( buffer + i, buffer_len- i < max_dbg_str_len ? - buffer_len - i : max_dbg_str_len ); - - ::OutputDebugStringA( s.c_str() ); - } - return true; - } - + virtual bool out_buffer(const char* buffer, int buffer_len , int log_level, int color, const char* plog_name = NULL) override; }; #endif - inline bool is_stdout_a_tty() - { - static std::atomic initialized(false); - static std::atomic is_a_tty(false); - - if (!initialized.load(std::memory_order_acquire)) - { -#if defined(WIN32) - is_a_tty.store(0 != _isatty(_fileno(stdout)), std::memory_order_relaxed); -#else - is_a_tty.store(0 != isatty(fileno(stdout)), std::memory_order_relaxed); -#endif - initialized.store(true, std::memory_order_release); - } - - return is_a_tty.load(std::memory_order_relaxed); - } - - inline void set_console_color(int color, bool bright) - { - if (!is_stdout_a_tty()) - return; - - switch(color) - { - case console_color_default: - { -#ifdef WIN32 - HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(h_stdout, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE| (bright ? FOREGROUND_INTENSITY:0)); -#else - if(bright) - std::cout << "\033[1;37m"; - else - std::cout << "\033[0m"; -#endif - } - break; - case console_color_white: - { -#ifdef WIN32 - HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(h_stdout, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | (bright ? FOREGROUND_INTENSITY:0)); -#else - if(bright) - std::cout << "\033[1;37m"; - else - std::cout << "\033[0;37m"; -#endif - } - break; - case console_color_red: - { -#ifdef WIN32 - HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(h_stdout, FOREGROUND_RED | (bright ? FOREGROUND_INTENSITY:0)); -#else - if(bright) - std::cout << "\033[1;31m"; - else - std::cout << "\033[0;31m"; -#endif - } - break; - case console_color_green: - { -#ifdef WIN32 - HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(h_stdout, FOREGROUND_GREEN | (bright ? FOREGROUND_INTENSITY:0)); -#else - if(bright) - std::cout << "\033[1;32m"; - else - std::cout << "\033[0;32m"; -#endif - } - break; - - case console_color_blue: - { -#ifdef WIN32 - HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(h_stdout, FOREGROUND_BLUE | FOREGROUND_INTENSITY);//(bright ? FOREGROUND_INTENSITY:0)); -#else - if(bright) - std::cout << "\033[1;34m"; - else - std::cout << "\033[0;34m"; -#endif - } - break; - - case console_color_cyan: - { -#ifdef WIN32 - HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(h_stdout, FOREGROUND_GREEN | FOREGROUND_BLUE | (bright ? FOREGROUND_INTENSITY:0)); -#else - if(bright) - std::cout << "\033[1;36m"; - else - std::cout << "\033[0;36m"; -#endif - } - break; - - case console_color_magenta: - { -#ifdef WIN32 - HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(h_stdout, FOREGROUND_BLUE | FOREGROUND_RED | (bright ? FOREGROUND_INTENSITY:0)); -#else - if(bright) - std::cout << "\033[1;35m"; - else - std::cout << "\033[0;35m"; -#endif - } - break; - - case console_color_yellow: - { -#ifdef WIN32 - HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(h_stdout, FOREGROUND_RED | FOREGROUND_GREEN | (bright ? FOREGROUND_INTENSITY:0)); -#else - if(bright) - std::cout << "\033[1;33m"; - else - std::cout << "\033[0;33m"; -#endif - } - break; - - } - } - - inline void reset_console_color() { - if (!is_stdout_a_tty()) - return; - -#ifdef WIN32 - HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(h_stdout, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); -#else - std::cout << "\033[0m"; - std::cout.flush(); -#endif - } - class console_output_stream: public ibase_log_stream { #ifdef _MSC_VER @@ -357,129 +156,22 @@ namespace log_space #endif public: - console_output_stream() - { -#ifdef _MSC_VER - - if(!::GetStdHandle(STD_OUTPUT_HANDLE)) - m_have_to_kill_console = true; - else - m_have_to_kill_console = false; - - ::AllocConsole(); -#endif - } - - ~console_output_stream() - { -#ifdef _MSC_VER - if(m_have_to_kill_console) - ::FreeConsole(); -#endif - } - int get_type(){return LOGGER_CONSOLE;} - - - - virtual bool out_buffer( const char* buffer, int buffer_len , int log_level, int color, const char* plog_name = NULL) - { - if(plog_name) - return true; //skip alternative logs from console - - set_console_color(color, log_level < 1); - -#ifdef _MSC_VER - const char* ptarget_buf = NULL; - char* pallocated_buf = NULL; - - // - int i = 0; - for(; i < buffer_len; i++) - if(buffer[i] == '\a') break; - if(i == buffer_len) - ptarget_buf = buffer; - else - { - pallocated_buf = new char[buffer_len]; - ptarget_buf = pallocated_buf; - for(i = 0; i < buffer_len; i++) - { - if(buffer[i] == '\a') - pallocated_buf[i] = '^'; - else - pallocated_buf[i] = buffer[i]; - } - } - - //uint32_t b = 0; - //::WriteConsoleA(::GetStdHandle(STD_OUTPUT_HANDLE), ptarget_buf, buffer_len, (DWORD*)&b, 0); - std::cout << ptarget_buf; - if(pallocated_buf) delete [] pallocated_buf; -#else - std::string buf(buffer, buffer_len); - for(size_t i = 0; i!= buf.size(); i++) - { - if(buf[i] == 7 || buf[i] == -107) - buf[i] = '^'; - } - - std::cout << buf; -#endif - reset_console_color(); - return true; - } - + console_output_stream(); + virtual ~console_output_stream(); + virtual int get_type() const override { return LOGGER_CONSOLE; } + virtual bool out_buffer(const char* buffer, int buffer_len , int log_level, int color, const char* plog_name = NULL) override; }; - inline bool rotate_log_file(const char* pfile_path) - { -#ifdef _MSC_VER - if(!pfile_path) - return false; - - std::string file_path = pfile_path; - std::string::size_type a = file_path .rfind('.'); - if ( a != std::string::npos ) - file_path .erase( a, file_path .size()); - - ::DeleteFileA( (file_path + ".0").c_str() ); - ::MoveFileA( (file_path + ".log").c_str(), (file_path + ".0").c_str() ); -#else - return false;//not implemented yet -#endif - return true; - } - - - - //--------------------------------------------------------------------------// class file_output_stream : public ibase_log_stream { public: typedef std::map named_log_streams; - file_output_stream( std::string default_log_file_name, std::string log_path ) - { - m_default_log_filename = default_log_file_name; - m_max_logfile_size = 0; - m_default_log_path = log_path; - m_pdefault_file_stream = add_new_stream_and_open(default_log_file_name.c_str()); - } + file_output_stream(std::string default_log_file_name, std::string log_path); + ~file_output_stream(); - ~file_output_stream() - { - for(named_log_streams::iterator it = m_log_file_names.begin(); it!=m_log_file_names.end(); it++) - { - if ( it->second->is_open() ) - { - it->second->flush(); - it->second->close(); - } - delete it->second; - } - } private: named_log_streams m_log_file_names; std::string m_default_log_path; @@ -488,109 +180,14 @@ namespace log_space std::string m_default_log_filename; uint64_t m_max_logfile_size; + virtual int get_type() const override { return LOGGER_FILE; } + virtual bool out_buffer(const char* buffer, int buffer_len, int log_level, int color, const char* plog_name = NULL) override; + virtual bool set_max_logfile_size(uint64_t max_size) override; + virtual bool set_log_rotate_cmd(const std::string& cmd) override; - std::ofstream* add_new_stream_and_open(const char* pstream_name) - { - //log_space::rotate_log_file((m_default_log_path + "\\" + pstream_name).c_str()); - - std::ofstream* pstream = (m_log_file_names[pstream_name] = new std::ofstream); - std::string target_path = m_default_log_path + "/" + pstream_name; - pstream->open( target_path.c_str(), std::ios_base::out | std::ios::app /*ios_base::trunc */); - if(pstream->fail()) - return NULL; - return pstream; - } - - bool set_max_logfile_size(uint64_t max_size) - { - m_max_logfile_size = max_size; - return true; - } - - bool set_log_rotate_cmd(const std::string& cmd) - { - m_log_rotate_cmd = cmd; - return true; - } - - - - virtual bool out_buffer( const char* buffer, int buffer_len, int log_level, int color, const char* plog_name = NULL ) - { - std::ofstream* m_target_file_stream = m_pdefault_file_stream; - if(plog_name) - { //find named stream - named_log_streams::iterator it = m_log_file_names.find(plog_name); - if(it == m_log_file_names.end()) - m_target_file_stream = add_new_stream_and_open(plog_name); - else - m_target_file_stream = it->second; - } - if(!m_target_file_stream || !m_target_file_stream->is_open()) - return false;//TODO: add assert here - - m_target_file_stream->write(buffer, buffer_len ); - m_target_file_stream->flush(); - - if(m_max_logfile_size) - { - std::ofstream::pos_type pt = m_target_file_stream->tellp(); - uint64_t current_sz = pt; - if(current_sz > m_max_logfile_size) - { - std::cout << "current_sz= " << current_sz << " m_max_logfile_size= " << m_max_logfile_size << std::endl; - std::string log_file_name; - if(!plog_name) - log_file_name = m_default_log_filename; - else - log_file_name = plog_name; - - m_target_file_stream->close(); - std::string new_log_file_name = log_file_name; - - time_t tm = 0; - time(&tm); - - int err_count = 0; - boost::system::error_code ec; - do - { - new_log_file_name = string_tools::cut_off_extension(log_file_name); - if(err_count) - new_log_file_name += misc_utils::get_time_str_v2(tm) + "(" + boost::lexical_cast(err_count) + ")" + ".log"; - else - new_log_file_name += misc_utils::get_time_str_v2(tm) + ".log"; - - err_count++; - }while(boost::filesystem::exists(m_default_log_path + "/" + new_log_file_name, ec)); - - std::string new_log_file_path = m_default_log_path + "/" + new_log_file_name; - boost::filesystem::rename(m_default_log_path + "/" + log_file_name, new_log_file_path, ec); - if(ec) - { - std::cout << "Filed to rename, ec = " << ec.message() << std::endl; - } - - if(m_log_rotate_cmd.size()) - { - - std::string m_log_rotate_cmd_local_copy = m_log_rotate_cmd; - //boost::replace_all(m_log_rotate_cmd, "[*SOURCE*]", new_log_file_path); - boost::replace_all(m_log_rotate_cmd_local_copy, "[*TARGET*]", new_log_file_path); - - misc_utils::call_sys_cmd(m_log_rotate_cmd_local_copy); - } - - m_target_file_stream->open( (m_default_log_path + "/" + log_file_name).c_str(), std::ios_base::out | std::ios::app /*ios_base::trunc */); - if(m_target_file_stream->fail()) - return false; - } - } - - return true; - } - int get_type(){return LOGGER_FILE;} + std::ofstream* add_new_stream_and_open(const char* pstream_name); }; + /************************************************************************/ /* */ /************************************************************************/ @@ -599,272 +196,59 @@ namespace log_space public: typedef std::list > streams_container; - log_stream_splitter(){} - ~log_stream_splitter() - { - //free pointers - std::for_each(m_log_streams.begin(), m_log_streams.end(), delete_ptr()); - } + log_stream_splitter() { } + ~log_stream_splitter(); - bool set_max_logfile_size(uint64_t max_size) - { - for(streams_container::iterator it = m_log_streams.begin(); it!=m_log_streams.end();it++) - it->first->set_max_logfile_size(max_size); - return true; - } + bool set_max_logfile_size(uint64_t max_size); + bool set_log_rotate_cmd(const std::string& cmd); + bool do_log_message(const std::string& rlog_mes, int log_level, int color, const char* plog_name = NULL); + bool add_logger(int type, const char* pdefault_file_name, const char* pdefault_log_folder, int log_level_limit = LOG_LEVEL_4); + bool add_logger(ibase_log_stream* pstream, int log_level_limit = LOG_LEVEL_4); + bool remove_logger(int type); - bool set_log_rotate_cmd(const std::string& cmd) - { - for(streams_container::iterator it = m_log_streams.begin(); it!=m_log_streams.end();it++) - it->first->set_log_rotate_cmd(cmd); - return true; - } - - bool do_log_message(const std::string& rlog_mes, int log_level, int color, const char* plog_name = NULL) - { - std::string str_mess = rlog_mes; - size_t str_len = str_mess.size(); - const char* pstr = str_mess.c_str(); - for(streams_container::iterator it = m_log_streams.begin(); it!=m_log_streams.end();it++) - if(it->second >= log_level) - it->first->out_buffer(pstr, (int)str_len, log_level, color, plog_name); - return true; - } - - bool add_logger( int type, const char* pdefault_file_name, const char* pdefault_log_folder, int log_level_limit = LOG_LEVEL_4 ) - { - ibase_log_stream* ls = NULL; - - switch( type ) - { - case LOGGER_FILE: - ls = new file_output_stream( pdefault_file_name, pdefault_log_folder ); - break; - - case LOGGER_DEBUGGER: -#ifdef _MSC_VER - ls = new debug_output_stream( ); -#else - return false;//not implemented yet -#endif - break; - case LOGGER_CONSOLE: - ls = new console_output_stream( ); - break; - } - - if ( ls ) { - m_log_streams.push_back(streams_container::value_type(ls, log_level_limit)); - return true; - } - return ls ? true:false; - } - bool add_logger( ibase_log_stream* pstream, int log_level_limit = LOG_LEVEL_4 ) - { - m_log_streams.push_back(streams_container::value_type(pstream, log_level_limit) ); - return true; - } - - bool remove_logger(int type) - { - streams_container::iterator it = m_log_streams.begin(); - for(;it!=m_log_streams.end(); it++) - { - if(it->first->get_type() == type) - { - delete it->first; - m_log_streams.erase(it); - return true; - } - } - return false; - - } - - protected: private: - - streams_container m_log_streams; + streams_container m_log_streams; }; /************************************************************************/ /* */ /************************************************************************/ - inline int get_set_log_detalisation_level(bool is_need_set = false, int log_level_to_set = LOG_LEVEL_1); - inline int get_set_time_level(bool is_need_set = false, int time_log_level = LOG_LEVEL_0); - inline bool get_set_need_thread_id(bool is_need_set = false, bool is_need_val = false); - inline bool get_set_need_proc_name(bool is_need_set = false, bool is_need_val = false); + int get_set_log_detalisation_level(bool is_need_set = false, int log_level_to_set = LOG_LEVEL_1); + int get_set_time_level(bool is_need_set = false, int time_log_level = LOG_LEVEL_0); + bool get_set_need_thread_id(bool is_need_set = false, bool is_need_val = false); + bool get_set_need_proc_name(bool is_need_set = false, bool is_need_val = false); + std::string get_daytime_string2(); + std::string get_day_time_string(); + std::string get_time_string(); - inline std::string get_daytime_string2() - { - boost::posix_time::ptime p = boost::posix_time::microsec_clock::local_time(); - return misc_utils::get_time_str_v3(p); - } - inline std::string get_day_time_string() - { - return get_daytime_string2(); - //time_t tm = 0; - //time(&tm); - //return misc_utils::get_time_str(tm); - } - - inline std::string get_time_string() - { - return get_daytime_string2(); - - } #ifdef _MSC_VER - inline std::string get_time_string_adv(SYSTEMTIME* pst = NULL) - { - SYSTEMTIME st = {0}; - if(!pst) - { - pst = &st; - GetSystemTime(&st); - } - std::stringstream str_str; - str_str.fill('0'); - str_str << std::setw(2) << pst->wHour << "_" - << std::setw(2) << pst->wMinute << "_" - << std::setw(2) << pst->wSecond << "_" - << std::setw(3) << pst->wMilliseconds; - return str_str.str(); - } + inline std::string get_time_string_adv(SYSTEMTIME* pst = NULL); #endif - - - - - class logger + class logger { public: friend class log_singletone; - logger() - { - CRITICAL_REGION_BEGIN(m_critical_sec); - init(); - CRITICAL_REGION_END(); - } - ~logger() - { - } + logger(); + ~logger() { } - bool set_max_logfile_size(uint64_t max_size) - { - CRITICAL_REGION_BEGIN(m_critical_sec); - m_log_target.set_max_logfile_size(max_size); - CRITICAL_REGION_END(); - return true; - } + bool set_max_logfile_size(uint64_t max_size); + bool set_log_rotate_cmd(const std::string& cmd); + bool take_away_journal(std::list& journal); + bool do_log_message(const std::string& rlog_mes, int log_level, int color, bool add_to_journal = false, const char* plog_name = NULL); + bool add_logger(int type, const char* pdefault_file_name, const char* pdefault_log_folder , int log_level_limit = LOG_LEVEL_4); + bool add_logger(ibase_log_stream* pstream, int log_level_limit = LOG_LEVEL_4); + bool remove_logger(int type); + bool set_thread_prefix(const std::string& prefix); + std::string get_default_log_file() { return m_default_log_file; } + std::string get_default_log_folder() { return m_default_log_folder; } - bool set_log_rotate_cmd(const std::string& cmd) - { - CRITICAL_REGION_BEGIN(m_critical_sec); - m_log_target.set_log_rotate_cmd(cmd); - CRITICAL_REGION_END(); - return true; - } - - bool take_away_journal(std::list& journal) - { - CRITICAL_REGION_BEGIN(m_critical_sec); - m_journal.swap(journal); - CRITICAL_REGION_END(); - return true; - } - - bool do_log_message(const std::string& rlog_mes, int log_level, int color, bool add_to_journal = false, const char* plog_name = NULL) - { - CRITICAL_REGION_BEGIN(m_critical_sec); - m_log_target.do_log_message(rlog_mes, log_level, color, plog_name); - if(add_to_journal) - m_journal.push_back(rlog_mes); - - return true; - CRITICAL_REGION_END(); - } - - bool add_logger( int type, const char* pdefault_file_name, const char* pdefault_log_folder , int log_level_limit = LOG_LEVEL_4) - { - CRITICAL_REGION_BEGIN(m_critical_sec); - return m_log_target.add_logger( type, pdefault_file_name, pdefault_log_folder, log_level_limit); - CRITICAL_REGION_END(); - } - bool add_logger( ibase_log_stream* pstream, int log_level_limit = LOG_LEVEL_4) - { - CRITICAL_REGION_BEGIN(m_critical_sec); - return m_log_target.add_logger(pstream, log_level_limit); - CRITICAL_REGION_END(); - } - - bool remove_logger(int type) - { - CRITICAL_REGION_BEGIN(m_critical_sec); - return m_log_target.remove_logger(type); - CRITICAL_REGION_END(); - } - - - bool set_thread_prefix(const std::string& prefix) - { - CRITICAL_REGION_BEGIN(m_critical_sec); - m_thr_prefix_strings[misc_utils::get_thread_string_id()] = prefix; - CRITICAL_REGION_END(); - return true; - } - - - std::string get_default_log_file() - { - return m_default_log_file; - } - - std::string get_default_log_folder() - { - return m_default_log_folder; - } - - protected: private: - bool init() - { - // - - m_process_name = string_tools::get_current_module_name(); - - init_log_path_by_default(); - - //init default set of loggers - init_default_loggers(); - - std::stringstream ss; - ss << get_time_string() << " Init logging. Level=" << get_set_log_detalisation_level() - << " Log path=" << m_default_log_folder << std::endl; - this->do_log_message(ss.str(), console_color_white, LOG_LEVEL_0); - return true; - } - bool init_default_loggers() - { - //TODO: - return true; - } - - bool init_log_path_by_default() - { - //load process name - m_default_log_folder = string_tools::get_current_module_folder(); - - m_default_log_file = m_process_name; - std::string::size_type a = m_default_log_file.rfind('.'); - if ( a != std::string::npos ) - m_default_log_file.erase( a, m_default_log_file.size()); - m_default_log_file += ".log"; - - return true; - } + bool init(); + bool init_default_loggers(); + bool init_log_path_by_default(); log_stream_splitter m_log_target; @@ -875,6 +259,7 @@ namespace log_space std::list m_journal; critical_section m_critical_sec; }; + /************************************************************************/ /* */ /************************************************************************/ @@ -883,372 +268,63 @@ namespace log_space public: friend class initializer; friend class logger; - static int get_log_detalisation_level() - { - get_or_create_instance();//to initialize logger, if it not initialized - return get_set_log_detalisation_level(); - } - static bool is_filter_error(int error_code) - { - return false; - } + static int get_log_detalisation_level(); + static bool is_filter_error(int error_code); + static bool do_log_message(const std::string& rlog_mes, int log_level, int color, bool keep_in_journal, const char* plog_name = NULL); + static bool take_away_journal(std::list& journal); + static bool set_max_logfile_size(uint64_t file_size); + static bool set_log_rotate_cmd(const std::string& cmd); + static bool add_logger(int type, const char* pdefault_file_name, const char* pdefault_log_folder, int log_level_limit = LOG_LEVEL_4); + static std::string get_default_log_file(); + static std::string get_default_log_folder(); + static bool add_logger( ibase_log_stream* pstream, int log_level_limit = LOG_LEVEL_4); + static bool remove_logger(int type); - - static bool do_log_message(const std::string& rlog_mes, int log_level, int color, bool keep_in_journal, const char* plog_name = NULL) - { - logger* plogger = get_or_create_instance(); - bool res = false; - if(plogger) - res = plogger->do_log_message(rlog_mes, log_level, color, keep_in_journal, plog_name); - else - { //globally uninitialized, create new logger for each call of do_log_message() and then delete it - plogger = new logger(); - //TODO: some extra initialization - res = plogger->do_log_message(rlog_mes, log_level, color, keep_in_journal, plog_name); - delete plogger; - plogger = NULL; - } - return res; - } - - static bool take_away_journal(std::list& journal) - { - logger* plogger = get_or_create_instance(); - bool res = false; - if(plogger) - res = plogger->take_away_journal(journal); - - return res; - } - - static bool set_max_logfile_size(uint64_t file_size) - { - logger* plogger = get_or_create_instance(); - if(!plogger) return false; - return plogger->set_max_logfile_size(file_size); - } - - - static bool set_log_rotate_cmd(const std::string& cmd) - { - logger* plogger = get_or_create_instance(); - if(!plogger) return false; - return plogger->set_log_rotate_cmd(cmd); - } - - - static bool add_logger( int type, const char* pdefault_file_name, const char* pdefault_log_folder, int log_level_limit = LOG_LEVEL_4) - { - logger* plogger = get_or_create_instance(); - if(!plogger) return false; - return plogger->add_logger(type, pdefault_file_name, pdefault_log_folder, log_level_limit); - } - - static std::string get_default_log_file() - { - logger* plogger = get_or_create_instance(); - if(plogger) - return plogger->get_default_log_file(); - - return ""; - } - - static std::string get_default_log_folder() - { - logger* plogger = get_or_create_instance(); - if(plogger) - return plogger->get_default_log_folder(); - - return ""; - } - - static bool add_logger( ibase_log_stream* pstream, int log_level_limit = LOG_LEVEL_4 ) - { - logger* plogger = get_or_create_instance(); - if(!plogger) return false; - return plogger->add_logger(pstream, log_level_limit); - } - - - static bool remove_logger( int type ) - { - logger* plogger = get_or_create_instance(); - if(!plogger) return false; - return plogger->remove_logger(type); - } PUSH_WARNINGS DISABLE_GCC_WARNING(maybe-uninitialized) - static int get_set_log_detalisation_level(bool is_need_set = false, int log_level_to_set = LOG_LEVEL_1) - { - static int log_detalisation_level = LOG_LEVEL_1; - if(is_need_set) - log_detalisation_level = log_level_to_set; - return log_detalisation_level; - } + static int get_set_log_detalisation_level(bool is_need_set = false, int log_level_to_set = LOG_LEVEL_1); POP_WARNINGS - static int get_set_time_level(bool is_need_set = false, int time_log_level = LOG_LEVEL_0) - { - static int val_time_log_level = LOG_LEVEL_0; - if(is_need_set) - val_time_log_level = time_log_level; - - return val_time_log_level; - } - - static int get_set_process_level(bool is_need_set = false, int process_log_level = LOG_LEVEL_0) - { - static int val_process_log_level = LOG_LEVEL_0; - if(is_need_set) - val_process_log_level = process_log_level; - - return val_process_log_level; - } - - /*static int get_set_tid_level(bool is_need_set = false, int tid_log_level = LOG_LEVEL_0) - { - static int val_tid_log_level = LOG_LEVEL_0; - if(is_need_set) - val_tid_log_level = tid_log_level; - - return val_tid_log_level; - }*/ - - static bool get_set_need_thread_id(bool is_need_set = false, bool is_need_val = false) - { - static bool is_need = false; - if(is_need_set) - is_need = is_need_val; - - return is_need; - } - - static bool get_set_need_proc_name(bool is_need_set = false, bool is_need_val = false) - { - static bool is_need = true; - if(is_need_set) - is_need = is_need_val; - - return is_need; - } - static uint64_t get_set_err_count(bool is_need_set = false, uint64_t err_val = false) - { - static uint64_t err_count = 0; - if(is_need_set) - err_count = err_val; - - return err_count; - } + static int get_set_time_level(bool is_need_set = false, int time_log_level = LOG_LEVEL_0); + static int get_set_process_level(bool is_need_set = false, int process_log_level = LOG_LEVEL_0); + static bool get_set_need_thread_id(bool is_need_set = false, bool is_need_val = false); + static bool get_set_need_proc_name(bool is_need_set = false, bool is_need_val = false); + static uint64_t get_set_err_count(bool is_need_set = false, uint64_t err_val = false); #ifdef _MSC_VER - - - static void SetThreadName( DWORD dwThreadID, const char* threadName) - { -#define MS_VC_EXCEPTION 0x406D1388 - -#pragma pack(push,8) - typedef struct tagTHREADNAME_INFO - { - DWORD dwType; // Must be 0x1000. - LPCSTR szName; // Pointer to name (in user addr space). - DWORD dwThreadID; // Thread ID (-1=caller thread). - DWORD dwFlags; // Reserved for future use, must be zero. - } THREADNAME_INFO; -#pragma pack(pop) - - - - Sleep(10); - THREADNAME_INFO info; - info.dwType = 0x1000; - info.szName = (char*)threadName; - info.dwThreadID = dwThreadID; - info.dwFlags = 0; - - __try - { - RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info ); - } - __except(EXCEPTION_EXECUTE_HANDLER) - { - } - } + static void SetThreadName( DWORD dwThreadID, const char* threadName); #endif - static bool set_thread_log_prefix(const std::string& prefix) - { -#ifdef _MSC_VER - SetThreadName(-1, prefix.c_str()); -#endif - - - logger* plogger = get_or_create_instance(); - if(!plogger) return false; - return plogger->set_thread_prefix(prefix); - } - - - static std::string get_prefix_entry() - { - std::stringstream str_prefix; - //write time entry - if ( get_set_time_level() <= get_set_log_detalisation_level() ) - str_prefix << get_day_time_string() << " "; - - //write process info - logger* plogger = get_or_create_instance(); - //bool res = false; - if(!plogger) - { //globally uninitialized, create new logger for each call of get_prefix_entry() and then delete it - plogger = new logger(); - } - - //if ( get_set_need_proc_name() && get_set_process_level() <= get_set_log_detalisation_level() ) - // str_prefix << "[" << plogger->m_process_name << " (id=" << GetCurrentProcessId() << ")] "; -//#ifdef _MSC_VER_EX - if ( get_set_need_thread_id() /*&& get_set_tid_level() <= get_set_log_detalisation_level()*/ ) - str_prefix << "tid:" << misc_utils::get_thread_string_id() << " "; -//#endif - - if(plogger->m_thr_prefix_strings.size()) - { - CRITICAL_REGION_LOCAL(plogger->m_critical_sec); - std::string thr_str = misc_utils::get_thread_string_id(); - std::map::iterator it = plogger->m_thr_prefix_strings.find(thr_str); - if(it!=plogger->m_thr_prefix_strings.end()) - { - str_prefix << it->second; - } - } - - - if(get_set_is_uninitialized()) - delete plogger; - - return str_prefix.str(); - } + static bool set_thread_log_prefix(const std::string& prefix); + static std::string get_prefix_entry(); private: - log_singletone(){}//restric to create an instance + log_singletone() { } //restric to create an instance //static initializer m_log_initializer;//must be in one .cpp file (for example main.cpp) via DEFINE_LOGGING macro - static bool init() - { - return true;/*do nothing here*/ - } - static bool un_init() - { - //delete object - logger* plogger = get_set_instance_internal(); - if(plogger) delete plogger; - //set uninitialized - get_set_is_uninitialized(true, true); - get_set_instance_internal(true, NULL); - return true; - } + static bool init(); + static bool un_init(); - static logger* get_or_create_instance() - { - logger* plogger = get_set_instance_internal(); - if(!plogger) - if(!get_set_is_uninitialized()) - get_set_instance_internal(true, plogger = new logger); - - return plogger; - } - - static logger* get_set_instance_internal(bool is_need_set = false, logger* pnew_logger_val = NULL) - { - static logger* val_plogger = NULL; - - if(is_need_set) - val_plogger = pnew_logger_val; - - return val_plogger; - } - - static bool get_set_is_uninitialized(bool is_need_set = false, bool is_uninitialized = false) - { - static bool val_is_uninitialized = false; - - if(is_need_set) - val_is_uninitialized = is_uninitialized; - - return val_is_uninitialized; - } - //static int get_set_error_filter(bool is_need_set = false) + static logger* get_or_create_instance(); + static logger* get_set_instance_internal(bool is_need_set = false, logger* pnew_logger_val = NULL); + static bool get_set_is_uninitialized(bool is_need_set = false, bool is_uninitialized = false); }; const static initializer log_initializer; - /************************************************************************/ - /* */ -// /************************************************************************/ -// class log_array_value -// { -// int num; -// log_message& m_ref_log_mes; -// -// public: -// -// log_array_value( log_message& log_mes ) : num(0), m_ref_log_mes(log_mes) {} -// -// void operator ( )( ibase_log_value *val ) { -// m_ref_log_mes << "\n[" << num++ << "] "/* << val*/; } -// -// -// template -// void operator ()(T &value ) -// { -// m_ref_log_mes << "\n[" << num++ << "] " << value; -// } -// }; class log_frame { - std::string m_name; - int m_level; - const char* m_plog_name; + std::string m_name; + int m_level; + const char* m_plog_name; + public: - - log_frame(const std::string& name, int dlevel = LOG_LEVEL_2 , const char* plog_name = NULL) - { -#ifdef _MSC_VER - int lasterr=::GetLastError(); -#endif - m_plog_name = plog_name; - if ( dlevel <= log_singletone::get_log_detalisation_level() ) - { - m_name = name; - std::stringstream ss; - ss << log_space::log_singletone::get_prefix_entry() << "-->>" << m_name << std::endl; - log_singletone::do_log_message(ss.str(), dlevel, console_color_default, false, m_plog_name); - } - m_level = dlevel; -#ifdef _MSC_VER - ::SetLastError(lasterr); -#endif - } - ~log_frame() - { -#ifdef _MSC_VER - int lasterr=::GetLastError(); -#endif - - if (m_level <= log_singletone::get_log_detalisation_level() ) - { - std::stringstream ss; - ss << log_space::log_singletone::get_prefix_entry() << "<<--" << m_name << std::endl; - log_singletone::do_log_message(ss.str(), m_level, console_color_default, false,m_plog_name); - } -#ifdef _MSC_VER - ::SetLastError(lasterr); -#endif - } + log_frame(const std::string& name, int dlevel = LOG_LEVEL_2 , const char* plog_name = NULL); + ~log_frame(); }; - inline int get_set_time_level(bool is_need_set, int time_log_level) + inline int get_set_time_level(bool is_need_set, int time_log_level) { return log_singletone::get_set_time_level(is_need_set, time_log_level); } @@ -1269,41 +345,10 @@ POP_WARNINGS return log_singletone::get_set_need_proc_name(is_need_set, is_need_val); } - inline std::string get_win32_err_descr(int err_no) - { -#ifdef _MSC_VER - LPVOID lpMsgBuf; - - FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM, - NULL, - err_no, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (char*) &lpMsgBuf, - 0, NULL ); - - std::string fix_sys_message = "(null)"; - if(lpMsgBuf) fix_sys_message = (char*)lpMsgBuf; - std::string::size_type a; - if ( (a = fix_sys_message.rfind( '\n' )) != std::string::npos ) - fix_sys_message.erase(a); - if ( (a = fix_sys_message.rfind( '\r' )) != std::string::npos ) - fix_sys_message.erase(a); - - LocalFree(lpMsgBuf); - return fix_sys_message; -#else - return "Not implemented yet"; -#endif - } - - inline bool getwin32_err_text(std::stringstream& ref_message, int error_no) - { - ref_message << "win32 error:" << get_win32_err_descr(error_no); - return true; - } + inline std::string get_win32_err_descr(int err_no); + inline bool getwin32_err_text(std::stringstream& ref_message, int error_no); } + #if defined(_DEBUG) || defined(__GNUC__) #define ENABLE_LOGGING_INTERNAL #endif @@ -1342,6 +387,9 @@ POP_WARNINGS #define LOG_FRAME2(log_name, x, y) epee::log_space::log_frame frame(x, y, log_name) +#define LOG_WARNING2(log_name, x, y) {if ( y <= epee::log_space::log_singletone::get_log_detalisation_level() )\ + {std::stringstream ss________; ss________ << epee::log_space::log_singletone::get_prefix_entry() << "WARNING " << __FILE__ << ":" << __LINE__ << " " << x << std::endl; epee::log_space::log_singletone::do_log_message(ss________.str(), y, epee::log_space::console_color_red, true, log_name);LOCAL_ASSERT(0); epee::log_space::log_singletone::get_set_err_count(true, epee::log_space::log_singletone::get_set_err_count()+1);}} + #else @@ -1362,6 +410,8 @@ POP_WARNINGS #define LOG_FRAME2(log_name, x, y) +#define LOG_WARNING2(log_name, x, level) + #endif @@ -1400,6 +450,7 @@ POP_WARNINGS //#define LOGWIN_PLATFORM_ERROR(err_no) LOGWINDWOS_PLATFORM_ERROR2(LOG_DEFAULT_TARGET, err_no) #define LOG_SOCKET_ERROR(err_no) LOG_SOCKET_ERROR2(LOG_DEFAULT_TARGET, err_no) //#define LOGWIN_PLATFORM_ERROR_UNCRITICAL(mess) LOGWINDWOS_PLATFORM_ERROR_UNCRITICAL2(LOG_DEFAULT_TARGET, mess) +#define LOG_WARNING(mess, level) LOG_WARNING2(LOG_DEFAULT_TARGET, mess, level) #define ENDL std::endl diff --git a/contrib/epee/include/misc_os_dependent.cpp b/contrib/epee/include/misc_os_dependent.cpp new file mode 100644 index 00000000..3bff6585 --- /dev/null +++ b/contrib/epee/include/misc_os_dependent.cpp @@ -0,0 +1,97 @@ +// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of the Andrey N. Sabelnikov nor the +// names of its contributors may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#include "misc_os_dependent.h" + +#include + +#include + +#ifdef __MACH__ +#include +#include +#endif + +namespace epee +{ +namespace misc_utils +{ + + uint64_t get_tick_count() + { +#if defined(_MSC_VER) + return ::GetTickCount64(); +#elif defined(__MACH__) + clock_serv_t cclock; + mach_timespec_t mts; + + host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + + return (mts.tv_sec * 1000) + (mts.tv_nsec/1000000); +#else + struct timespec ts; + if(clock_gettime(CLOCK_MONOTONIC, &ts) != 0) { + return 0; + } + return (ts.tv_sec * 1000) + (ts.tv_nsec/1000000); +#endif + } + + int call_sys_cmd(const std::string& cmd) + { + std::cout << "# " << cmd << std::endl; + + FILE * fp ; + //char tstCommand[] ="ls *"; + char path[1000] = {0}; +#if !defined(__GNUC__) + fp = _popen(cmd.c_str(), "r"); +#else + fp = popen(cmd.c_str(), "r"); +#endif + while ( fgets( path, 1000, fp ) != NULL ) + std::cout << path; + +#if !defined(__GNUC__) + _pclose(fp); +#else + pclose(fp); +#endif + return 0; + } + + std::string get_thread_string_id() + { +#if defined(_MSC_VER) + return boost::lexical_cast(GetCurrentThreadId()); +#elif defined(__GNUC__) + return boost::lexical_cast(pthread_self()); +#endif + } +} +} diff --git a/contrib/epee/include/misc_os_dependent.h b/contrib/epee/include/misc_os_dependent.h index 4d9c991e..eb4a3a8a 100644 --- a/contrib/epee/include/misc_os_dependent.h +++ b/contrib/epee/include/misc_os_dependent.h @@ -23,86 +23,30 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // + +#pragma once + +#include +#include + #ifdef WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif - //#ifdef _WIN32_WINNT - // #undef _WIN32_WINNT - // #define _WIN32_WINNT 0x0600 - //#endif - - -#include + #if !defined(NOMINMAX) + #define NOMINMAX 1 + #endif // !defined(NOMINMAX) + + #include #endif -#ifdef __MACH__ -#include -#include -#endif - -#pragma once namespace epee { namespace misc_utils { - - inline uint64_t get_tick_count() - { -#if defined(_MSC_VER) - return ::GetTickCount64(); -#elif defined(__MACH__) - clock_serv_t cclock; - mach_timespec_t mts; - - host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); - clock_get_time(cclock, &mts); - mach_port_deallocate(mach_task_self(), cclock); - - return (mts.tv_sec * 1000) + (mts.tv_nsec/1000000); -#else - struct timespec ts; - if(clock_gettime(CLOCK_MONOTONIC, &ts) != 0) { - return 0; - } - return (ts.tv_sec * 1000) + (ts.tv_nsec/1000000); -#endif - } - - - inline int call_sys_cmd(const std::string& cmd) - { - std::cout << "# " << cmd << std::endl; - - FILE * fp ; - //char tstCommand[] ="ls *"; - char path[1000] = {0}; -#if !defined(__GNUC__) - fp = _popen(cmd.c_str(), "r"); -#else - fp = popen(cmd.c_str(), "r"); -#endif - while ( fgets( path, 1000, fp ) != NULL ) - std::cout << path; - -#if !defined(__GNUC__) - _pclose(fp); -#else - pclose(fp); -#endif - return 0; - - } - - - inline std::string get_thread_string_id() - { -#if defined(_MSC_VER) - return boost::lexical_cast(GetCurrentThreadId()); -#elif defined(__GNUC__) - return boost::lexical_cast(pthread_self()); -#endif - } + uint64_t get_tick_count(); + int call_sys_cmd(const std::string& cmd); + std::string get_thread_string_id(); } } diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl index 74f1e5c4..0265d57e 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.inl +++ b/contrib/epee/include/net/abstract_tcp_server2.inl @@ -30,10 +30,12 @@ #include #include #include +#include #include #include #include #include +#include "include_base_utils.h" #include "misc_language.h" #include "pragma_comp_defs.h" @@ -304,7 +306,7 @@ PRAGMA_WARNING_DISABLE_VS(4355) if(m_send_que.size() > ABSTRACT_SERVER_SEND_QUE_MAX_COUNT) { send_guard.unlock(); -// LOG_ERROR("send que size is more than ABSTRACT_SERVER_SEND_QUE_MAX_COUNT(" << ABSTRACT_SERVER_SEND_QUE_MAX_COUNT << "), shutting down connection"); + LOG_WARNING("send que size is more than ABSTRACT_SERVER_SEND_QUE_MAX_COUNT(" << ABSTRACT_SERVER_SEND_QUE_MAX_COUNT << "), shutting down connection", LOG_LEVEL_2); close(); return false; } diff --git a/contrib/epee/include/net/http_protocol_handler.h b/contrib/epee/include/net/http_protocol_handler.h index aed90977..72b4b575 100644 --- a/contrib/epee/include/net/http_protocol_handler.h +++ b/contrib/epee/include/net/http_protocol_handler.h @@ -31,6 +31,7 @@ #define _HTTP_SERVER_H_ #include +#include "include_base_utils.h" #include "net_utils_base.h" #include "to_nonconst_iterator.h" #include "http_base.h" diff --git a/contrib/epee/include/net/http_protocol_handler.inl b/contrib/epee/include/net/http_protocol_handler.inl index 78b46427..fc091a21 100644 --- a/contrib/epee/include/net/http_protocol_handler.inl +++ b/contrib/epee/include/net/http_protocol_handler.inl @@ -28,8 +28,10 @@ #include #include #include "http_protocol_handler.h" +#include "include_base_utils.h" #include "reg_exp_definer.h" #include "string_tools.h" +#include "time_helper.h" #include "file_io_utils.h" #include "net_parse_helpers.h" diff --git a/contrib/epee/include/net/http_server_handlers_map2.h b/contrib/epee/include/net/http_server_handlers_map2.h index 20146013..12ad9d97 100644 --- a/contrib/epee/include/net/http_server_handlers_map2.h +++ b/contrib/epee/include/net/http_server_handlers_map2.h @@ -28,6 +28,7 @@ #pragma once #include "http_base.h" #include "jsonrpc_structs.h" +#include "misc_os_dependent.h" #include "storages/portable_storage.h" #include "storages/portable_storage_template_helper.h" @@ -59,7 +60,7 @@ else if(query_info.m_URI == s_pattern) \ { \ handled = true; \ - uint64_t ticks = misc_utils::get_tick_count(); \ + uint64_t ticks = epee::misc_utils::get_tick_count(); \ boost::value_initialized req; \ bool parse_res = epee::serialization::load_t_from_json(static_cast(req), query_info.m_body); \ CHECK_AND_ASSERT_MES(parse_res, false, "Failed to parse json: \r\n" << query_info.m_body); \ @@ -84,11 +85,11 @@ else if(query_info.m_URI == s_pattern) \ { \ handled = true; \ - uint64_t ticks = misc_utils::get_tick_count(); \ + uint64_t ticks = epee::misc_utils::get_tick_count(); \ boost::value_initialized req; \ bool parse_res = epee::serialization::load_t_from_binary(static_cast(req), query_info.m_body); \ CHECK_AND_ASSERT_MES(parse_res, false, "Failed to parse bin body data, body size=" << query_info.m_body.size()); \ - uint64_t ticks1 = misc_utils::get_tick_count(); \ + uint64_t ticks1 = epee::misc_utils::get_tick_count(); \ boost::value_initialized resp;\ if(!callback_f(static_cast(req), static_cast(resp), m_conn_context)) \ { \ @@ -97,7 +98,7 @@ response_info.m_response_comment = "Internal Server Error"; \ return true; \ } \ - uint64_t ticks2 = misc_utils::get_tick_count(); \ + uint64_t ticks2 = epee::misc_utils::get_tick_count(); \ epee::serialization::store_t_to_binary(static_cast(resp), response_info.m_body); \ uint64_t ticks3 = epee::misc_utils::get_tick_count(); \ response_info.m_mime_tipe = " application/octet-stream"; \ diff --git a/contrib/epee/include/net/levin_protocol_handler_async.h b/contrib/epee/include/net/levin_protocol_handler_async.h index a286c472..406d92b2 100644 --- a/contrib/epee/include/net/levin_protocol_handler_async.h +++ b/contrib/epee/include/net/levin_protocol_handler_async.h @@ -25,6 +25,7 @@ // #pragma once +#include #include #include #include diff --git a/contrib/epee/include/net/net_parse_helpers.h b/contrib/epee/include/net/net_parse_helpers.h index 586dac98..40c3d935 100644 --- a/contrib/epee/include/net/net_parse_helpers.h +++ b/contrib/epee/include/net/net_parse_helpers.h @@ -29,6 +29,7 @@ #pragma once #include "http_base.h" +#include "include_base_utils.h" #include "reg_exp_definer.h" diff --git a/contrib/epee/include/net/net_utils_base.h b/contrib/epee/include/net/net_utils_base.h index b5619bab..b0e44f5d 100644 --- a/contrib/epee/include/net/net_utils_base.h +++ b/contrib/epee/include/net/net_utils_base.h @@ -36,6 +36,11 @@ #define MAKE_IP( a1, a2, a3, a4 ) (a1|(a2<<8)|(a3<<16)|(a4<<24)) #endif +namespace boost { + namespace asio { + class io_service; + } +} namespace epee { diff --git a/contrib/epee/include/profile_tools.h b/contrib/epee/include/profile_tools.h index 0e1646f6..49180c6a 100644 --- a/contrib/epee/include/profile_tools.h +++ b/contrib/epee/include/profile_tools.h @@ -28,6 +28,8 @@ #ifndef _PROFILE_TOOLS_H_ #define _PROFILE_TOOLS_H_ +#include + namespace epee { diff --git a/contrib/epee/include/serialization/keyvalue_serialization_overloads.h b/contrib/epee/include/serialization/keyvalue_serialization_overloads.h index 33486d9e..d52d09c0 100644 --- a/contrib/epee/include/serialization/keyvalue_serialization_overloads.h +++ b/contrib/epee/include/serialization/keyvalue_serialization_overloads.h @@ -26,6 +26,11 @@ #pragma once +#include + +#include +#include + namespace epee { namespace serialization diff --git a/contrib/epee/include/static_initializer.h b/contrib/epee/include/static_initializer.h index 3463a560..1510805c 100644 --- a/contrib/epee/include/static_initializer.h +++ b/contrib/epee/include/static_initializer.h @@ -44,38 +44,11 @@ public: initializer() { to_initialize::init(); - //get_set_is_initialized(true, true); } ~initializer() { to_initialize::un_init(); - //get_set_is_uninitialized(true, true); } - - /*static inline bool is_initialized() - { - return get_set_is_initialized(); - } - static inline bool is_uninitialized() - { - return get_set_is_uninitialized(); - } - -private: - static inline bool get_set_is_initialized(bool need_to_set = false, bool val_to_set= false) - { - static bool val_is_initialized = false; - if(need_to_set) - val_is_initialized = val_to_set; - return val_is_initialized; - } - static inline bool get_set_is_uninitialized(bool need_to_set = false, bool val_to_set = false) - { - static bool val_is_uninitialized = false; - if(need_to_set) - val_is_uninitialized = val_to_set; - return val_is_uninitialized; - }*/ }; } diff --git a/contrib/epee/include/storages/portable_storage.h b/contrib/epee/include/storages/portable_storage.h index bbfe5f85..87c6edb2 100644 --- a/contrib/epee/include/storages/portable_storage.h +++ b/contrib/epee/include/storages/portable_storage.h @@ -150,7 +150,8 @@ namespace epee m_root.m_entries.clear(); if(source.size() < sizeof(storage_block_header)) { - LOG_ERROR("portable_storage: wrong binary format, packet size = " << source.size() << " less than expected sizeof(storage_block_header)=" << sizeof(storage_block_header)); + LOG_WARNING("portable_storage: wrong binary format, packet size = " << source.size() << " less than expected sizeof(storage_block_header)=" + << sizeof(storage_block_header), LOG_LEVEL_2) return false; } storage_block_header* pbuff = (storage_block_header*)source.data(); @@ -158,12 +159,12 @@ namespace epee pbuff->m_signature_b != PORTABLE_STORAGE_SIGNATUREB ) { - LOG_ERROR("portable_storage: wrong binary format - signature missmatch"); + LOG_WARNING("portable_storage: wrong binary format - signature missmatch", LOG_LEVEL_2); return false; } if(pbuff->m_ver != PORTABLE_STORAGE_FORMAT_VER) { - LOG_ERROR("portable_storage: wrong binary format - unknown format ver = " << pbuff->m_ver); + LOG_WARNING("portable_storage: wrong binary format - unknown format ver = " << pbuff->m_ver, LOG_LEVEL_2); return false; } TRY_ENTRY(); diff --git a/contrib/epee/include/storages/portable_storage_from_json.h b/contrib/epee/include/storages/portable_storage_from_json.h index 4e74fb7a..d3d84cc6 100644 --- a/contrib/epee/include/storages/portable_storage_from_json.h +++ b/contrib/epee/include/storages/portable_storage_from_json.h @@ -25,6 +25,9 @@ // #pragma once + +#include + #include "parserse_base_utils.h" #include "file_io_utils.h" @@ -365,6 +368,7 @@ namespace epee } catch(const std::exception& ex) { + (void)(ex); LOG_PRINT_RED_L0("Failed to parse json, what: " << ex.what()); return false; } diff --git a/contrib/epee/include/storages/portable_storage_to_bin.h b/contrib/epee/include/storages/portable_storage_to_bin.h index baf90290..6743a608 100644 --- a/contrib/epee/include/storages/portable_storage_to_bin.h +++ b/contrib/epee/include/storages/portable_storage_to_bin.h @@ -30,6 +30,7 @@ #include "misc_language.h" #include "portable_storage_base.h" +#include "pragma_comp_defs.h" namespace epee { diff --git a/contrib/epee/include/storages/portable_storage_val_converters.h b/contrib/epee/include/storages/portable_storage_val_converters.h index 6ea50588..73d339f5 100644 --- a/contrib/epee/include/storages/portable_storage_val_converters.h +++ b/contrib/epee/include/storages/portable_storage_val_converters.h @@ -24,10 +24,10 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // - - #pragma once +#include + #include "misc_language.h" #include "portable_storage_base.h" #include "warnings.h" diff --git a/contrib/epee/include/string_tools.cpp b/contrib/epee/include/string_tools.cpp new file mode 100644 index 00000000..8181bc10 --- /dev/null +++ b/contrib/epee/include/string_tools.cpp @@ -0,0 +1,487 @@ +// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of the Andrey N. Sabelnikov nor the +// names of its contributors may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#include "string_tools.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +#ifdef WINDOWS_PLATFORM +#pragma comment (lib, "Rpcrt4.lib") +#endif + +namespace epee +{ +namespace string_tools +{ + std::wstring get_str_from_guid(const boost::uuids::uuid& rid) + { + return boost::lexical_cast(rid); + } + //---------------------------------------------------------------------------- + std::string get_str_from_guid_a(const boost::uuids::uuid& rid) + { + return boost::lexical_cast(rid); + } + //---------------------------------------------------------------------------- + bool get_guid_from_string( boost::uuids::uuid& inetifer, std::wstring str_id) + { + if(str_id.size() < 36) + return false; + + if('{' == *str_id.begin()) + str_id.erase(0, 1); + + if('}' == *(--str_id.end())) + str_id.erase(--str_id.end()); + + try + { + inetifer = boost::lexical_cast(str_id); + return true; + } + catch(...) + { + return false; + } + } + //---------------------------------------------------------------------------- + bool get_guid_from_string(OUT boost::uuids::uuid& inetifer, const std::string& str_id) + { + std::string local_str_id = str_id; + if(local_str_id.size() < 36) + return false; + + if('{' == *local_str_id.begin()) + local_str_id.erase(0, 1); + + if('}' == *(--local_str_id.end())) + local_str_id.erase(--local_str_id.end()); + + try + { + inetifer = boost::lexical_cast(local_str_id); + return true; + } + catch(...) + { + return false; + } + } + //---------------------------------------------------------------------------- +//#ifdef _WINSOCK2API_ + std::string get_ip_string_from_int32(uint32_t ip) + { + in_addr adr; + adr.s_addr = ip; + const char* pbuf = inet_ntoa(adr); + if(pbuf) + return pbuf; + else + return "[failed]"; + } + //---------------------------------------------------------------------------- + bool get_ip_int32_from_string(uint32_t& ip, const std::string& ip_str) + { + ip = inet_addr(ip_str.c_str()); + if(INADDR_NONE == ip) + return false; + + return true; + } + //---------------------------------------------------------------------------- + bool parse_peer_from_string(uint32_t& ip, uint32_t& port, const std::string& addres) + { + //parse ip and address + std::string::size_type p = addres.find(':'); + if(p == std::string::npos) + { + return false; + } + std::string ip_str = addres.substr(0, p); + std::string port_str = addres.substr(p+1, addres.size()); + + if(!get_ip_int32_from_string(ip, ip_str)) + { + return false; + } + + if(!get_xtype_from_string(port, port_str)) + { + return false; + } + return true; + } + + //---------------------------------------------------------------------------- + std::string num_to_string_fast(int64_t val) + { + /* + char buff[30] = {0}; + i64toa_s(val, buff, sizeof(buff)-1, 10); + return buff;*/ + return boost::lexical_cast(val); + } + //---------------------------------------------------------------------------- + bool string_to_num_fast(const std::string& buff, int64_t& val) + { + //return get_xtype_from_string(val, buff); +#if (defined _MSC_VER) + val = _atoi64(buff.c_str()); +#else + val = atoll(buff.c_str()); +#endif + /* + * val = atoi64(buff.c_str()); + */ + if(buff != "0" && val == 0) + return false; + return true; + } + //---------------------------------------------------------------------------- + bool string_to_num_fast(const std::string& buff, int& val) + { + val = atoi(buff.c_str()); + if(buff != "0" && val == 0) + return false; + + return true; + } + //---------------------------------------------------------------------------- +#ifdef WINDOWS_PLATFORM + std::string system_time_to_string(const SYSTEMTIME& st) + { + + /* + TIME_ZONE_INFORMATION tzi; + GetTimeZoneInformation(&tzi); + SystemTimeToTzSpecificLocalTime(&tzi, &stUTC, &stLocal); + */ + + char szTime[25], szDate[25]; + ::GetTimeFormatA( + LOCALE_USER_DEFAULT, // locale + TIME_FORCE24HOURFORMAT, // options + &st, // time + NULL, // time format string + szTime, // formatted string buffer + 25 // size of string buffer + ); + + ::GetDateFormatA( + LOCALE_USER_DEFAULT, // locale + NULL, // options + &st, // date + NULL, // date format + szDate, // formatted string buffer + 25 // size of buffer + ); + szTime[24] = szDate[24] = 0; //be happy :) + + std::string res = szDate; + (res += " " )+= szTime; + return res; + + } +#endif + //---------------------------------------------------------------------------- + + bool compare_no_case(const std::string& str1, const std::string& str2) + { + + return !boost::iequals(str1, str2); + } + //---------------------------------------------------------------------------- + bool compare_no_case(const std::wstring& str1, const std::wstring& str2) + { + return !boost::iequals(str1, str2); + } + //---------------------------------------------------------------------------- + bool is_match_prefix(const std::wstring& str1, const std::wstring& prefix) + { + if(prefix.size()>str1.size()) + return false; + + if(!compare_no_case(str1.substr(0, prefix.size()), prefix)) + return true; + else + return false; + } + //---------------------------------------------------------------------------- + bool is_match_prefix(const std::string& str1, const std::string& prefix) + { + if(prefix.size()>str1.size()) + return false; + + if(!compare_no_case(str1.substr(0, prefix.size()), prefix)) + return true; + else + return false; + } + //---------------------------------------------------------------------------- + std::string& get_current_module_name() + { + static std::string module_name; + return module_name; + } + //---------------------------------------------------------------------------- + std::string& get_current_module_folder() + { + static std::string module_folder; + return module_folder; + } + //---------------------------------------------------------------------------- +#ifdef _WIN32 + std::string get_current_module_path() + { + char pname [5000] = {0}; + GetModuleFileNameA( NULL, pname, sizeof(pname)); + pname[sizeof(pname)-1] = 0; //be happy ;) + return pname; + } +#endif + //---------------------------------------------------------------------------- + bool set_module_name_and_folder(const std::string& path_to_process_) + { + std::string path_to_process = path_to_process_; +#ifdef _WIN32 + path_to_process = get_current_module_path(); +#endif + std::string::size_type a = path_to_process.rfind( '\\' ); + if(a == std::string::npos ) + { + a = path_to_process.rfind( '/' ); + } + if ( a != std::string::npos ) + { + get_current_module_name() = path_to_process.substr(a+1, path_to_process.size()); + get_current_module_folder() = path_to_process.substr(0, a); + return true; + }else + return false; + + } + + //---------------------------------------------------------------------------- + bool trim_left(std::string& str) + { + for(std::string::iterator it = str.begin(); it!= str.end() && isspace(static_cast(*it));) + str.erase(str.begin()); + + return true; + } + //---------------------------------------------------------------------------- + bool trim_right(std::string& str) + { + + for(std::string::reverse_iterator it = str.rbegin(); it!= str.rend() && isspace(static_cast(*it));) + str.erase( --((it++).base())); + + return true; + } + //---------------------------------------------------------------------------- + std::string& trim(std::string& str) + { + + trim_left(str); + trim_right(str); + return str; + } + //---------------------------------------------------------------------------- + std::string trim(const std::string& str_) + { + std::string str = str_; + trim_left(str); + trim_right(str); + return str; + } + //---------------------------------------------------------------------------- + std::string get_extension(const std::string& str) + { + std::string res; + std::string::size_type pos = str.rfind('.'); + if(std::string::npos == pos) + return res; + + res = str.substr(pos+1, str.size()-pos); + return res; + } + //---------------------------------------------------------------------------- + std::string get_filename_from_path(const std::string& str) + { + std::string res; + std::string::size_type pos = str.rfind('\\'); + if(std::string::npos == pos) + return str; + + res = str.substr(pos+1, str.size()-pos); + return res; + } + //---------------------------------------------------------------------------- + + + + std::string cut_off_extension(const std::string& str) + { + std::string res; + std::string::size_type pos = str.rfind('.'); + if(std::string::npos == pos) + return str; + + res = str.substr(0, pos); + return res; + } + + //---------------------------------------------------------------------------- +#ifdef _WININET_ + std::string get_string_from_systemtime(const SYSTEMTIME& sys_time) + { + std::string result_string; + + char buff[100] = {0}; + BOOL res = ::InternetTimeFromSystemTimeA(&sys_time, INTERNET_RFC1123_FORMAT, buff, 99); + if(!res) + { + LOG_ERROR("Failed to load SytemTime to string"); + } + + result_string = buff; + return result_string; + + } + //------------------------------------------------------------------------------------- + SYSTEMTIME get_systemtime_from_string(const std::string& buff) + { + SYSTEMTIME result_time = {0}; + + BOOL res = ::InternetTimeToSystemTimeA(buff.c_str(), &result_time, NULL); + if(!res) + { + LOG_ERROR("Failed to load SytemTime from string " << buff << "interval set to 15 minutes"); + } + + return result_time; + } +#endif + +#ifdef WINDOWS_PLATFORM + const wchar_t* get_pc_name() + { + static wchar_t info[INFO_BUFFER_SIZE]; + static DWORD bufCharCount = INFO_BUFFER_SIZE; + static bool init = false; + + if (!init) { + if (!GetComputerNameW( info, &bufCharCount )) + info[0] = 0; + else + init = true; + } + + return info; + } + + const wchar_t* get_user_name() + { + static wchar_t info[INFO_BUFFER_SIZE]; + static DWORD bufCharCount = INFO_BUFFER_SIZE; + static bool init = false; + + if (!init) { + if (!GetUserNameW( info, &bufCharCount )) + info[0] = 0; + else + init = true; + } + + return info; + } +#endif + +#ifdef _LM_ + const wchar_t* get_domain_name() + { + static wchar_t info[INFO_BUFFER_SIZE]; + static DWORD bufCharCount = 0; + static bool init = false; + + if (!init) { + LPWSTR domain( NULL ); + NETSETUP_JOIN_STATUS status; + info[0] = 0; + + if (NET_API_STATUS result = NetGetJoinInformation( NULL, &domain, &status )) + { + LOG_ERROR("get_domain_name error: " << log_space::get_win32_err_descr(result)); + } else + { + StringCchCopyW( info, sizeof(info)/sizeof( info[0] ), domain ); + NetApiBufferFree((void*)domain); + init = true; + } + } + + return info; + } +#endif +#ifdef WINDOWS_PLATFORM + inline + std::string load_resource_string_a(int id, const char* pmodule_name = NULL) + { + //slow realization + HMODULE h = ::GetModuleHandleA( pmodule_name ); + + char buff[2000] = {0}; + + ::LoadStringA( h, id, buff, sizeof(buff)); + buff[sizeof(buff)-1] = 0; //be happy :) + return buff; + } + inline + std::wstring load_resource_string_w(int id, const char* pmodule_name = NULL) + { + //slow realization + HMODULE h = ::GetModuleHandleA( pmodule_name ); + + wchar_t buff[2000] = {0}; + + ::LoadStringW( h, id, buff, sizeof(buff) / sizeof( buff[0] ) ); + buff[(sizeof(buff)/sizeof(buff[0]))-1] = 0; //be happy :) + return buff; + } +#endif +} +} diff --git a/contrib/epee/include/string_tools.h b/contrib/epee/include/string_tools.h index 8289ee0b..281fbd94 100644 --- a/contrib/epee/include/string_tools.h +++ b/contrib/epee/include/string_tools.h @@ -24,24 +24,21 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // - +#pragma once #ifndef _STRING_TOOLS_H_ #define _STRING_TOOLS_H_ -//#include -#include -#include +#include +#include #include -//#include -#include -#include -#include -#include -#include -#include -#include "warnings.h" +#include +#include +#include +#include +#include +#include "warnings.h" #ifndef OUT #define OUT @@ -51,71 +48,32 @@ #pragma comment (lib, "Rpcrt4.lib") #endif +// Don't include lexical_cast.hpp, to reduce compilation time +//#include + +namespace boost { + namespace uuids { + struct uuid; + } + + template + inline Target lexical_cast(const Source &arg); +} + namespace epee { namespace string_tools { - inline std::wstring get_str_from_guid(const boost::uuids::uuid& rid) - { - return boost::lexical_cast(rid); - } - //---------------------------------------------------------------------------- - inline std::string get_str_from_guid_a(const boost::uuids::uuid& rid) - { - return boost::lexical_cast(rid); - } - //---------------------------------------------------------------------------- - inline bool get_guid_from_string( boost::uuids::uuid& inetifer, std::wstring str_id) - { - if(str_id.size() < 36) - return false; - - if('{' == *str_id.begin()) - str_id.erase(0, 1); - - if('}' == *(--str_id.end())) - str_id.erase(--str_id.end()); - - try - { - inetifer = boost::lexical_cast(str_id); - return true; - } - catch(...) - { - return false; - } - } - //---------------------------------------------------------------------------- - inline bool get_guid_from_string(OUT boost::uuids::uuid& inetifer, const std::string& str_id) - { - std::string local_str_id = str_id; - if(local_str_id.size() < 36) - return false; - - if('{' == *local_str_id.begin()) - local_str_id.erase(0, 1); - - if('}' == *(--local_str_id.end())) - local_str_id.erase(--local_str_id.end()); - - try - { - inetifer = boost::lexical_cast(local_str_id); - return true; - } - catch(...) - { - return false; - } - } - //---------------------------------------------------------------------------- + std::wstring get_str_from_guid(const boost::uuids::uuid& rid); + std::string get_str_from_guid_a(const boost::uuids::uuid& rid); + bool get_guid_from_string( boost::uuids::uuid& inetifer, std::wstring str_id); + bool get_guid_from_string(OUT boost::uuids::uuid& inetifer, const std::string& str_id); + //---------------------------------------------------------------------------- template std::basic_string buff_to_hex(const std::basic_string& s) { - using namespace std; - basic_stringstream hexStream; - hexStream << hex << noshowbase << setw(2); + std::basic_stringstream hexStream; + hexStream << std::hex << std::noshowbase << std::setw(2); for(typename std::basic_string::const_iterator it = s.begin(); it != s.end(); it++) { @@ -127,13 +85,12 @@ namespace string_tools template std::basic_string buff_to_hex_nodelimer(const std::basic_string& s) { - using namespace std; - basic_stringstream hexStream; - hexStream << hex << noshowbase; + std::basic_stringstream hexStream; + hexStream << std::hex << std::noshowbase; for(typename std::basic_string::const_iterator it = s.begin(); it != s.end(); it++) { - hexStream << setw(2) << setfill('0') << static_cast(static_cast(*it)); + hexStream << std::setw(2) << std::setfill('0') << static_cast(static_cast(*it)); } return hexStream.str(); } @@ -273,13 +230,6 @@ POP_WARNINGS return true; } -/* template - bool get_xparam_from_command_line(const std::map& res, const std::basic_string & key, t_type& val) - { - - } - */ - template bool get_xparam_from_command_line(const std::map& res, const t_string & key, t_type& val) { @@ -295,22 +245,22 @@ POP_WARNINGS return true; } - template - t_type get_xparam_from_command_line(const std::map& res, const t_string & key, const t_type& default_value) - { - typename std::map::const_iterator it = res.find(key); - if(it == res.end()) - return default_value; + template + t_type get_xparam_from_command_line(const std::map& res, const t_string & key, const t_type& default_value) + { + typename std::map::const_iterator it = res.find(key); + if(it == res.end()) + return default_value; - if(it->second.size()) - { - t_type s; - get_xtype_from_string(s, it->second); - return s; - } + if(it->second.size()) + { + t_type s; + get_xtype_from_string(s, it->second); + return s; + } - return default_value; - } + return default_value; + } template bool have_in_command_line(const std::map& res, const std::basic_string& key) @@ -322,243 +272,40 @@ POP_WARNINGS return true; } - //---------------------------------------------------------------------------- -//#ifdef _WINSOCK2API_ - inline std::string get_ip_string_from_int32(uint32_t ip) - { - in_addr adr; - adr.s_addr = ip; - const char* pbuf = inet_ntoa(adr); - if(pbuf) - return pbuf; - else - return "[failed]"; - } - //---------------------------------------------------------------------------- - inline bool get_ip_int32_from_string(uint32_t& ip, const std::string& ip_str) - { - ip = inet_addr(ip_str.c_str()); - if(INADDR_NONE == ip) - return false; - - return true; - } //---------------------------------------------------------------------------- - inline bool parse_peer_from_string(uint32_t& ip, uint32_t& port, const std::string& addres) - { - //parse ip and address - std::string::size_type p = addres.find(':'); - if(p == std::string::npos) - { - return false; - } - std::string ip_str = addres.substr(0, p); - std::string port_str = addres.substr(p+1, addres.size()); - - if(!get_ip_int32_from_string(ip, ip_str)) - { - return false; - } - - if(!get_xtype_from_string(port, port_str)) - { - return false; - } - return true; - } - -//#endif - //---------------------------------------------------------------------------- + std::string get_ip_string_from_int32(uint32_t ip); + bool get_ip_int32_from_string(uint32_t& ip, const std::string& ip_str); + bool parse_peer_from_string(uint32_t& ip, uint32_t& port, const std::string& addres); + //---------------------------------------------------------------------------- template - inline std::string get_t_as_hex_nwidth(const t& v, std::streamsize w = 8) + inline std::string get_t_as_hex_nwidth(const t& v, std::streamsize w = 8) { std::stringstream ss; ss << std::setfill ('0') << std::setw (w) << std::hex << std::noshowbase; ss << v; return ss.str(); } - - inline std::string num_to_string_fast(int64_t val) - { - /* - char buff[30] = {0}; - i64toa_s(val, buff, sizeof(buff)-1, 10); - return buff;*/ - return boost::lexical_cast(val); - } - //---------------------------------------------------------------------------- - inline bool string_to_num_fast(const std::string& buff, int64_t& val) - { - //return get_xtype_from_string(val, buff); -#if (defined _MSC_VER) - val = _atoi64(buff.c_str()); -#else - val = atoll(buff.c_str()); -#endif - /* - * val = atoi64(buff.c_str()); - */ - if(buff != "0" && val == 0) - return false; - return true; - } - //---------------------------------------------------------------------------- - inline bool string_to_num_fast(const std::string& buff, int& val) - { - val = atoi(buff.c_str()); - if(buff != "0" && val == 0) - return false; - - return true; - } - //---------------------------------------------------------------------------- + //---------------------------------------------------------------------------- + std::string num_to_string_fast(int64_t val); + bool string_to_num_fast(const std::string& buff, int64_t& val); + bool string_to_num_fast(const std::string& buff, int& val); #ifdef WINDOWS_PLATFORM - inline std::string system_time_to_string(const SYSTEMTIME& st) - { - - /* - TIME_ZONE_INFORMATION tzi; - GetTimeZoneInformation(&tzi); - SystemTimeToTzSpecificLocalTime(&tzi, &stUTC, &stLocal); - */ - - char szTime[25], szDate[25]; - ::GetTimeFormatA( - LOCALE_USER_DEFAULT, // locale - TIME_FORCE24HOURFORMAT, // options - &st, // time - NULL, // time format string - szTime, // formatted string buffer - 25 // size of string buffer - ); - - ::GetDateFormatA( - LOCALE_USER_DEFAULT, // locale - NULL, // options - &st, // date - NULL, // date format - szDate, // formatted string buffer - 25 // size of buffer - ); - szTime[24] = szDate[24] = 0; //be happy :) - - std::string res = szDate; - (res += " " )+= szTime; - return res; - - } + std::string system_time_to_string(const SYSTEMTIME& st); #endif - //---------------------------------------------------------------------------- - - inline bool compare_no_case(const std::string& str1, const std::string& str2) - { - - return !boost::iequals(str1, str2); - } - //---------------------------------------------------------------------------- - inline bool compare_no_case(const std::wstring& str1, const std::wstring& str2) - { - return !boost::iequals(str1, str2); - } - //---------------------------------------------------------------------------- - inline bool is_match_prefix(const std::wstring& str1, const std::wstring& prefix) - { - if(prefix.size()>str1.size()) - return false; - - if(!compare_no_case(str1.substr(0, prefix.size()), prefix)) - return true; - else - return false; - } - //---------------------------------------------------------------------------- - inline bool is_match_prefix(const std::string& str1, const std::string& prefix) - { - if(prefix.size()>str1.size()) - return false; - - if(!compare_no_case(str1.substr(0, prefix.size()), prefix)) - return true; - else - return false; - } - //---------------------------------------------------------------------------- - inline std::string& get_current_module_name() - { - static std::string module_name; - return module_name; - } - //---------------------------------------------------------------------------- - inline std::string& get_current_module_folder() - { - static std::string module_folder; - return module_folder; - } - //---------------------------------------------------------------------------- + bool compare_no_case(const std::string& str1, const std::string& str2); + bool compare_no_case(const std::wstring& str1, const std::wstring& str2); + bool is_match_prefix(const std::wstring& str1, const std::wstring& prefix); + bool is_match_prefix(const std::string& str1, const std::string& prefix); + std::string& get_current_module_name(); + std::string& get_current_module_folder(); #ifdef _WIN32 - inline std::string get_current_module_path() - { - char pname [5000] = {0}; - GetModuleFileNameA( NULL, pname, sizeof(pname)); - pname[sizeof(pname)-1] = 0; //be happy ;) - return pname; - } + std::string get_current_module_path(); #endif - //---------------------------------------------------------------------------- - inline bool set_module_name_and_folder(const std::string& path_to_process_) - { - std::string path_to_process = path_to_process_; -#ifdef _WIN32 - path_to_process = get_current_module_path(); -#endif - std::string::size_type a = path_to_process.rfind( '\\' ); - if(a == std::string::npos ) - { - a = path_to_process.rfind( '/' ); - } - if ( a != std::string::npos ) - { - get_current_module_name() = path_to_process.substr(a+1, path_to_process.size()); - get_current_module_folder() = path_to_process.substr(0, a); - return true; - }else - return false; - - } - - //---------------------------------------------------------------------------- - inline bool trim_left(std::string& str) - { - for(std::string::iterator it = str.begin(); it!= str.end() && isspace(static_cast(*it));) - str.erase(str.begin()); - - return true; - } - //---------------------------------------------------------------------------- - inline bool trim_right(std::string& str) - { - - for(std::string::reverse_iterator it = str.rbegin(); it!= str.rend() && isspace(static_cast(*it));) - str.erase( --((it++).base())); - - return true; - } - //---------------------------------------------------------------------------- - inline std::string& trim(std::string& str) - { - - trim_left(str); - trim_right(str); - return str; - } - //---------------------------------------------------------------------------- - inline std::string trim(const std::string& str_) - { - std::string str = str_; - trim_left(str); - trim_right(str); - return str; - } + bool set_module_name_and_folder(const std::string& path_to_process_); + bool trim_left(std::string& str); + bool trim_right(std::string& str); + std::string& trim(std::string& str); + std::string trim(const std::string& str_); //---------------------------------------------------------------------------- template std::string pod_to_hex(const t_pod_type& s) @@ -584,161 +331,27 @@ POP_WARNINGS return true; } //---------------------------------------------------------------------------- - inline std::string get_extension(const std::string& str) - { - std::string res; - std::string::size_type pos = str.rfind('.'); - if(std::string::npos == pos) - return res; - - res = str.substr(pos+1, str.size()-pos); - return res; - } - //---------------------------------------------------------------------------- - inline std::string get_filename_from_path(const std::string& str) - { - std::string res; - std::string::size_type pos = str.rfind('\\'); - if(std::string::npos == pos) - return str; - - res = str.substr(pos+1, str.size()-pos); - return res; - } - //---------------------------------------------------------------------------- - - - - inline std::string cut_off_extension(const std::string& str) - { - std::string res; - std::string::size_type pos = str.rfind('.'); - if(std::string::npos == pos) - return str; - - res = str.substr(0, pos); - return res; - } - - //---------------------------------------------------------------------------- + std::string get_extension(const std::string& str); + std::string get_filename_from_path(const std::string& str); + std::string cut_off_extension(const std::string& str); #ifdef _WININET_ - inline std::string get_string_from_systemtime(const SYSTEMTIME& sys_time) - { - std::string result_string; - - char buff[100] = {0}; - BOOL res = ::InternetTimeFromSystemTimeA(&sys_time, INTERNET_RFC1123_FORMAT, buff, 99); - if(!res) - { - LOG_ERROR("Failed to load SytemTime to string"); - } - - result_string = buff; - return result_string; - - } - //------------------------------------------------------------------------------------- - inline SYSTEMTIME get_systemtime_from_string(const std::string& buff) - { - SYSTEMTIME result_time = {0}; - - BOOL res = ::InternetTimeToSystemTimeA(buff.c_str(), &result_time, NULL); - if(!res) - { - LOG_ERROR("Failed to load SytemTime from string " << buff << "interval set to 15 minutes"); - } - - return result_time; - } + std::string get_string_from_systemtime(const SYSTEMTIME& sys_time); + SYSTEMTIME get_systemtime_from_string(const std::string& buff); #endif #ifdef WINDOWS_PLATFORM - static const DWORD INFO_BUFFER_SIZE = 10000; + const DWORD INFO_BUFFER_SIZE = 10000; - static const wchar_t* get_pc_name() - { - static wchar_t info[INFO_BUFFER_SIZE]; - static DWORD bufCharCount = INFO_BUFFER_SIZE; - static bool init = false; - - if (!init) { - if (!GetComputerNameW( info, &bufCharCount )) - info[0] = 0; - else - init = true; - } - - return info; - } - - static const wchar_t* get_user_name() - { - static wchar_t info[INFO_BUFFER_SIZE]; - static DWORD bufCharCount = INFO_BUFFER_SIZE; - static bool init = false; - - if (!init) { - if (!GetUserNameW( info, &bufCharCount )) - info[0] = 0; - else - init = true; - } - - return info; - } + const wchar_t* get_pc_name(); + const wchar_t* get_user_name(); #endif #ifdef _LM_ - static const wchar_t* get_domain_name() - { - static wchar_t info[INFO_BUFFER_SIZE]; - static DWORD bufCharCount = 0; - static bool init = false; - - if (!init) { - LPWSTR domain( NULL ); - NETSETUP_JOIN_STATUS status; - info[0] = 0; - - if (NET_API_STATUS result = NetGetJoinInformation( NULL, &domain, &status )) - { - LOG_ERROR("get_domain_name error: " << log_space::get_win32_err_descr(result)); - } else - { - StringCchCopyW( info, sizeof(info)/sizeof( info[0] ), domain ); - NetApiBufferFree((void*)domain); - init = true; - } - } - - return info; - } + const wchar_t* get_domain_name(); #endif #ifdef WINDOWS_PLATFORM - inline - std::string load_resource_string_a(int id, const char* pmodule_name = NULL) - { - //slow realization - HMODULE h = ::GetModuleHandleA( pmodule_name ); - - char buff[2000] = {0}; - - ::LoadStringA( h, id, buff, sizeof(buff)); - buff[sizeof(buff)-1] = 0; //be happy :) - return buff; - } - inline - std::wstring load_resource_string_w(int id, const char* pmodule_name = NULL) - { - //slow realization - HMODULE h = ::GetModuleHandleA( pmodule_name ); - - wchar_t buff[2000] = {0}; - - ::LoadStringW( h, id, buff, sizeof(buff) / sizeof( buff[0] ) ); - buff[(sizeof(buff)/sizeof(buff[0]))-1] = 0; //be happy :) - return buff; - } + std::string load_resource_string_a(int id, const char* pmodule_name = NULL); + std::wstring load_resource_string_w(int id, const char* pmodule_name = NULL); #endif } } diff --git a/contrib/epee/include/syncobj.h b/contrib/epee/include/syncobj.h index b7273da8..52cb70e6 100644 --- a/contrib/epee/include/syncobj.h +++ b/contrib/epee/include/syncobj.h @@ -32,13 +32,9 @@ #include #include -#include -#include -#include namespace epee { - struct simple_event { simple_event() : m_rised(false) @@ -70,7 +66,7 @@ namespace epee class critical_section { - boost::recursive_mutex m_section; + std::recursive_mutex m_section; public: //to make copy fake! @@ -89,7 +85,6 @@ namespace epee void lock() { m_section.lock(); - //EnterCriticalSection( &m_section ); } void unlock() diff --git a/external/google/dense_hash_map b/external/google/dense_hash_map new file mode 100644 index 00000000..74c796ba --- /dev/null +++ b/external/google/dense_hash_map @@ -0,0 +1,333 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// ---- +// Author: Craig Silverstein +// +// This is just a very thin wrapper over densehashtable.h, just +// like sgi stl's stl_hash_map is a very thin wrapper over +// stl_hashtable. The major thing we define is operator[], because +// we have a concept of a data_type which stl_hashtable doesn't +// (it only has a key and a value). +// +// NOTE: this is exactly like sparse_hash_map.h, with the word +// "sparse" replaced by "dense", except for the addition of +// set_empty_key(). +// +// YOU MUST CALL SET_EMPTY_KEY() IMMEDIATELY AFTER CONSTRUCTION. +// +// Otherwise your program will die in mysterious ways. (Note if you +// use the constructor that takes an InputIterator range, you pass in +// the empty key in the constructor, rather than after. As a result, +// this constructor differs from the standard STL version.) +// +// In other respects, we adhere mostly to the STL semantics for +// hash-map. One important exception is that insert() may invalidate +// iterators entirely -- STL semantics are that insert() may reorder +// iterators, but they all still refer to something valid in the +// hashtable. Not so for us. Likewise, insert() may invalidate +// pointers into the hashtable. (Whether insert invalidates iterators +// and pointers depends on whether it results in a hashtable resize). +// On the plus side, delete() doesn't invalidate iterators or pointers +// at all, or even change the ordering of elements. +// +// Here are a few "power user" tips: +// +// 1) set_deleted_key(): +// If you want to use erase() you *must* call set_deleted_key(), +// in addition to set_empty_key(), after construction. +// The deleted and empty keys must differ. +// +// 2) resize(0): +// When an item is deleted, its memory isn't freed right +// away. This allows you to iterate over a hashtable, +// and call erase(), without invalidating the iterator. +// To force the memory to be freed, call resize(0). +// For tr1 compatibility, this can also be called as rehash(0). +// +// 3) min_load_factor(0.0) +// Setting the minimum load factor to 0.0 guarantees that +// the hash table will never shrink. +// +// Roughly speaking: +// (1) dense_hash_map: fastest, uses the most memory unless entries are small +// (2) sparse_hash_map: slowest, uses the least memory +// (3) hash_map / unordered_map (STL): in the middle +// +// Typically I use sparse_hash_map when I care about space and/or when +// I need to save the hashtable on disk. I use hash_map otherwise. I +// don't personally use dense_hash_set ever; some people use it for +// small sets with lots of lookups. +// +// - dense_hash_map has, typically, about 78% memory overhead (if your +// data takes up X bytes, the hash_map uses .78X more bytes in overhead). +// - sparse_hash_map has about 4 bits overhead per entry. +// - sparse_hash_map can be 3-7 times slower than the others for lookup and, +// especially, inserts. See time_hash_map.cc for details. +// +// See /usr/(local/)?doc/sparsehash-*/dense_hash_map.html +// for information about how to use this class. + +#ifndef _DENSE_HASH_MAP_H_ +#define _DENSE_HASH_MAP_H_ + +#include +#include // for FILE * in read()/write() +#include // for the default template args +#include // for equal_to +#include // for alloc<> +#include // for pair<> +#include HASH_FUN_H // defined in config.h +#include +#include + + +_START_GOOGLE_NAMESPACE_ + +using STL_NAMESPACE::pair; + +template , // defined in sparseconfig.h + class EqualKey = STL_NAMESPACE::equal_to, + class Alloc = libc_allocator_with_realloc > > +class dense_hash_map { + private: + // Apparently select1st is not stl-standard, so we define our own + struct SelectKey { + typedef const Key& result_type; + const Key& operator()(const pair& p) const { + return p.first; + } + }; + struct SetKey { + void operator()(pair* value, const Key& new_key) const { + *const_cast(&value->first) = new_key; + // It would be nice to clear the rest of value here as well, in + // case it's taking up a lot of memory. We do this by clearing + // the value. This assumes T has a zero-arg constructor! + value->second = T(); + } + }; + // For operator[]. + struct DefaultValue { + STL_NAMESPACE::pair operator()(const Key& key) { + return STL_NAMESPACE::make_pair(key, T()); + } + }; + + // The actual data + typedef dense_hashtable, Key, HashFcn, SelectKey, + SetKey, EqualKey, Alloc> ht; + ht rep; + + public: + typedef typename ht::key_type key_type; + typedef T data_type; + typedef T mapped_type; + typedef typename ht::value_type value_type; + typedef typename ht::hasher hasher; + typedef typename ht::key_equal key_equal; + typedef Alloc allocator_type; + + typedef typename ht::size_type size_type; + typedef typename ht::difference_type difference_type; + typedef typename ht::pointer pointer; + typedef typename ht::const_pointer const_pointer; + typedef typename ht::reference reference; + typedef typename ht::const_reference const_reference; + + typedef typename ht::iterator iterator; + typedef typename ht::const_iterator const_iterator; + typedef typename ht::local_iterator local_iterator; + typedef typename ht::const_local_iterator const_local_iterator; + + // Iterator functions + iterator begin() { return rep.begin(); } + iterator end() { return rep.end(); } + const_iterator begin() const { return rep.begin(); } + const_iterator end() const { return rep.end(); } + + + // These come from tr1's unordered_map. For us, a bucket has 0 or 1 elements. + local_iterator begin(size_type i) { return rep.begin(i); } + local_iterator end(size_type i) { return rep.end(i); } + const_local_iterator begin(size_type i) const { return rep.begin(i); } + const_local_iterator end(size_type i) const { return rep.end(i); } + + // Accessor functions + allocator_type get_allocator() const { return rep.get_allocator(); } + hasher hash_funct() const { return rep.hash_funct(); } + hasher hash_function() const { return hash_funct(); } + key_equal key_eq() const { return rep.key_eq(); } + + + // Constructors + explicit dense_hash_map(size_type expected_max_items_in_table = 0, + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& alloc = allocator_type()) + : rep(expected_max_items_in_table, hf, eql, SelectKey(), SetKey(), alloc) { + } + + template + dense_hash_map(InputIterator f, InputIterator l, + const key_type& empty_key_val, + size_type expected_max_items_in_table = 0, + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& alloc = allocator_type()) + : rep(expected_max_items_in_table, hf, eql, SelectKey(), SetKey(), alloc) { + set_empty_key(empty_key_val); + rep.insert(f, l); + } + // We use the default copy constructor + // We use the default operator=() + // We use the default destructor + + void clear() { rep.clear(); } + // This clears the hash map without resizing it down to the minimum + // bucket count, but rather keeps the number of buckets constant + void clear_no_resize() { rep.clear_no_resize(); } + void swap(dense_hash_map& hs) { rep.swap(hs.rep); } + + + // Functions concerning size + size_type size() const { return rep.size(); } + size_type max_size() const { return rep.max_size(); } + bool empty() const { return rep.empty(); } + size_type bucket_count() const { return rep.bucket_count(); } + size_type max_bucket_count() const { return rep.max_bucket_count(); } + + // These are tr1 methods. bucket() is the bucket the key is or would be in. + size_type bucket_size(size_type i) const { return rep.bucket_size(i); } + size_type bucket(const key_type& key) const { return rep.bucket(key); } + float load_factor() const { + return size() * 1.0f / bucket_count(); + } + float max_load_factor() const { + float shrink, grow; + rep.get_resizing_parameters(&shrink, &grow); + return grow; + } + void max_load_factor(float new_grow) { + float shrink, grow; + rep.get_resizing_parameters(&shrink, &grow); + rep.set_resizing_parameters(shrink, new_grow); + } + // These aren't tr1 methods but perhaps ought to be. + float min_load_factor() const { + float shrink, grow; + rep.get_resizing_parameters(&shrink, &grow); + return shrink; + } + void min_load_factor(float new_shrink) { + float shrink, grow; + rep.get_resizing_parameters(&shrink, &grow); + rep.set_resizing_parameters(new_shrink, grow); + } + // Deprecated; use min_load_factor() or max_load_factor() instead. + void set_resizing_parameters(float shrink, float grow) { + rep.set_resizing_parameters(shrink, grow); + } + + void resize(size_type hint) { rep.resize(hint); } + void rehash(size_type hint) { resize(hint); } // the tr1 name + + // Lookup routines + iterator find(const key_type& key) { return rep.find(key); } + const_iterator find(const key_type& key) const { return rep.find(key); } + + data_type& operator[](const key_type& key) { // This is our value-add! + // If key is in the hashtable, returns find(key)->second, + // otherwise returns insert(value_type(key, T()).first->second. + // Note it does not create an empty T unless the find fails. + return rep.template find_or_insert(key).second; + } + + size_type count(const key_type& key) const { return rep.count(key); } + + pair equal_range(const key_type& key) { + return rep.equal_range(key); + } + pair equal_range(const key_type& key) const { + return rep.equal_range(key); + } + + // Insertion routines + pair insert(const value_type& obj) { return rep.insert(obj); } + template + void insert(InputIterator f, InputIterator l) { rep.insert(f, l); } + void insert(const_iterator f, const_iterator l) { rep.insert(f, l); } + // required for std::insert_iterator; the passed-in iterator is ignored + iterator insert(iterator, const value_type& obj) { return insert(obj).first; } + + + // Deletion and empty routines + // THESE ARE NON-STANDARD! I make you specify an "impossible" key + // value to identify deleted and empty buckets. You can change the + // deleted key as time goes on, or get rid of it entirely to be insert-only. + void set_empty_key(const key_type& key) { // YOU MUST CALL THIS! + rep.set_empty_key(value_type(key, data_type())); // rep wants a value + } + key_type empty_key() const { + return rep.empty_key().first; // rep returns a value + } + + void set_deleted_key(const key_type& key) { rep.set_deleted_key(key); } + void clear_deleted_key() { rep.clear_deleted_key(); } + key_type deleted_key() const { return rep.deleted_key(); } + + // These are standard + size_type erase(const key_type& key) { return rep.erase(key); } + void erase(iterator it) { rep.erase(it); } + void erase(iterator f, iterator l) { rep.erase(f, l); } + + + // Comparison + bool operator==(const dense_hash_map& hs) const { return rep == hs.rep; } + bool operator!=(const dense_hash_map& hs) const { return rep != hs.rep; } + + + // I/O -- this is an add-on for writing metainformation to disk + bool write_metadata(FILE *fp) { return rep.write_metadata(fp); } + bool read_metadata(FILE *fp) { return rep.read_metadata(fp); } + bool write_nopointer_data(FILE *fp) { return rep.write_nopointer_data(fp); } + bool read_nopointer_data(FILE *fp) { return rep.read_nopointer_data(fp); } +}; + +// We need a global swap as well +template +inline void swap(dense_hash_map& hm1, + dense_hash_map& hm2) { + hm1.swap(hm2); +} + +_END_GOOGLE_NAMESPACE_ + +#endif /* _DENSE_HASH_MAP_H_ */ diff --git a/external/google/dense_hash_set b/external/google/dense_hash_set new file mode 100644 index 00000000..fcf5db84 --- /dev/null +++ b/external/google/dense_hash_set @@ -0,0 +1,308 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// --- +// Author: Craig Silverstein +// +// This is just a very thin wrapper over densehashtable.h, just +// like sgi stl's stl_hash_set is a very thin wrapper over +// stl_hashtable. The major thing we define is operator[], because +// we have a concept of a data_type which stl_hashtable doesn't +// (it only has a key and a value). +// +// This is more different from dense_hash_map than you might think, +// because all iterators for sets are const (you obviously can't +// change the key, and for sets there is no value). +// +// NOTE: this is exactly like sparse_hash_set.h, with the word +// "sparse" replaced by "dense", except for the addition of +// set_empty_key(). +// +// YOU MUST CALL SET_EMPTY_KEY() IMMEDIATELY AFTER CONSTRUCTION. +// +// Otherwise your program will die in mysterious ways. (Note if you +// use the constructor that takes an InputIterator range, you pass in +// the empty key in the constructor, rather than after. As a result, +// this constructor differs from the standard STL version.) +// +// In other respects, we adhere mostly to the STL semantics for +// hash-map. One important exception is that insert() may invalidate +// iterators entirely -- STL semantics are that insert() may reorder +// iterators, but they all still refer to something valid in the +// hashtable. Not so for us. Likewise, insert() may invalidate +// pointers into the hashtable. (Whether insert invalidates iterators +// and pointers depends on whether it results in a hashtable resize). +// On the plus side, delete() doesn't invalidate iterators or pointers +// at all, or even change the ordering of elements. +// +// Here are a few "power user" tips: +// +// 1) set_deleted_key(): +// If you want to use erase() you must call set_deleted_key(), +// in addition to set_empty_key(), after construction. +// The deleted and empty keys must differ. +// +// 2) resize(0): +// When an item is deleted, its memory isn't freed right +// away. This allows you to iterate over a hashtable, +// and call erase(), without invalidating the iterator. +// To force the memory to be freed, call resize(0). +// For tr1 compatibility, this can also be called as rehash(0). +// +// 3) min_load_factor(0.0) +// Setting the minimum load factor to 0.0 guarantees that +// the hash table will never shrink. +// +// Roughly speaking: +// (1) dense_hash_set: fastest, uses the most memory unless entries are small +// (2) sparse_hash_set: slowest, uses the least memory +// (3) hash_set / unordered_set (STL): in the middle +// +// Typically I use sparse_hash_set when I care about space and/or when +// I need to save the hashtable on disk. I use hash_set otherwise. I +// don't personally use dense_hash_set ever; some people use it for +// small sets with lots of lookups. +// +// - dense_hash_set has, typically, about 78% memory overhead (if your +// data takes up X bytes, the hash_set uses .78X more bytes in overhead). +// - sparse_hash_set has about 4 bits overhead per entry. +// - sparse_hash_set can be 3-7 times slower than the others for lookup and, +// especially, inserts. See time_hash_map.cc for details. +// +// See /usr/(local/)?doc/sparsehash-*/dense_hash_set.html +// for information about how to use this class. + +#ifndef _DENSE_HASH_SET_H_ +#define _DENSE_HASH_SET_H_ + +#include +#include // for FILE * in read()/write() +#include // for the default template args +#include // for equal_to +#include // for alloc<> +#include // for pair<> +#include HASH_FUN_H // defined in config.h +#include +#include + + +_START_GOOGLE_NAMESPACE_ + +using STL_NAMESPACE::pair; + +template , // defined in sparseconfig.h + class EqualKey = STL_NAMESPACE::equal_to, + class Alloc = libc_allocator_with_realloc > +class dense_hash_set { + private: + // Apparently identity is not stl-standard, so we define our own + struct Identity { + typedef const Value& result_type; + const Value& operator()(const Value& v) const { return v; } + }; + struct SetKey { + void operator()(Value* value, const Value& new_key) const { + *value = new_key; + } + }; + + // The actual data + typedef dense_hashtable ht; + ht rep; + + public: + typedef typename ht::key_type key_type; + typedef typename ht::value_type value_type; + typedef typename ht::hasher hasher; + typedef typename ht::key_equal key_equal; + typedef Alloc allocator_type; + + typedef typename ht::size_type size_type; + typedef typename ht::difference_type difference_type; + typedef typename ht::const_pointer pointer; + typedef typename ht::const_pointer const_pointer; + typedef typename ht::const_reference reference; + typedef typename ht::const_reference const_reference; + + typedef typename ht::const_iterator iterator; + typedef typename ht::const_iterator const_iterator; + typedef typename ht::const_local_iterator local_iterator; + typedef typename ht::const_local_iterator const_local_iterator; + + + // Iterator functions -- recall all iterators are const + iterator begin() const { return rep.begin(); } + iterator end() const { return rep.end(); } + + // These come from tr1's unordered_set. For us, a bucket has 0 or 1 elements. + local_iterator begin(size_type i) const { return rep.begin(i); } + local_iterator end(size_type i) const { return rep.end(i); } + + + // Accessor functions + allocator_type get_allocator() const { return rep.get_allocator(); } + hasher hash_funct() const { return rep.hash_funct(); } + hasher hash_function() const { return hash_funct(); } // tr1 name + key_equal key_eq() const { return rep.key_eq(); } + + + // Constructors + explicit dense_hash_set(size_type expected_max_items_in_table = 0, + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& alloc = allocator_type()) + : rep(expected_max_items_in_table, hf, eql, Identity(), SetKey(), alloc) { + } + + template + dense_hash_set(InputIterator f, InputIterator l, + const key_type& empty_key_val, + size_type expected_max_items_in_table = 0, + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& alloc = allocator_type()) + : rep(expected_max_items_in_table, hf, eql, Identity(), SetKey(), alloc) { + set_empty_key(empty_key_val); + rep.insert(f, l); + } + // We use the default copy constructor + // We use the default operator=() + // We use the default destructor + + void clear() { rep.clear(); } + // This clears the hash set without resizing it down to the minimum + // bucket count, but rather keeps the number of buckets constant + void clear_no_resize() { rep.clear_no_resize(); } + void swap(dense_hash_set& hs) { rep.swap(hs.rep); } + + + // Functions concerning size + size_type size() const { return rep.size(); } + size_type max_size() const { return rep.max_size(); } + bool empty() const { return rep.empty(); } + size_type bucket_count() const { return rep.bucket_count(); } + size_type max_bucket_count() const { return rep.max_bucket_count(); } + + // These are tr1 methods. bucket() is the bucket the key is or would be in. + size_type bucket_size(size_type i) const { return rep.bucket_size(i); } + size_type bucket(const key_type& key) const { return rep.bucket(key); } + float load_factor() const { + return size() * 1.0f / bucket_count(); + } + float max_load_factor() const { + float shrink, grow; + rep.get_resizing_parameters(&shrink, &grow); + return grow; + } + void max_load_factor(float new_grow) { + float shrink, grow; + rep.get_resizing_parameters(&shrink, &grow); + rep.set_resizing_parameters(shrink, new_grow); + } + // These aren't tr1 methods but perhaps ought to be. + float min_load_factor() const { + float shrink, grow; + rep.get_resizing_parameters(&shrink, &grow); + return shrink; + } + void min_load_factor(float new_shrink) { + float shrink, grow; + rep.get_resizing_parameters(&shrink, &grow); + rep.set_resizing_parameters(new_shrink, grow); + } + // Deprecated; use min_load_factor() or max_load_factor() instead. + void set_resizing_parameters(float shrink, float grow) { + rep.set_resizing_parameters(shrink, grow); + } + + void resize(size_type hint) { rep.resize(hint); } + void rehash(size_type hint) { resize(hint); } // the tr1 name + + // Lookup routines + iterator find(const key_type& key) const { return rep.find(key); } + + size_type count(const key_type& key) const { return rep.count(key); } + + pair equal_range(const key_type& key) const { + return rep.equal_range(key); + } + + + // Insertion routines + pair insert(const value_type& obj) { + pair p = rep.insert(obj); + return pair(p.first, p.second); // const to non-const + } + template + void insert(InputIterator f, InputIterator l) { rep.insert(f, l); } + void insert(const_iterator f, const_iterator l) { rep.insert(f, l); } + // required for std::insert_iterator; the passed-in iterator is ignored + iterator insert(iterator, const value_type& obj) { return insert(obj).first; } + + + // Deletion and empty routines + // THESE ARE NON-STANDARD! I make you specify an "impossible" key + // value to identify deleted and empty buckets. You can change the + // deleted key as time goes on, or get rid of it entirely to be insert-only. + void set_empty_key(const key_type& key) { rep.set_empty_key(key); } + key_type empty_key() const { return rep.empty_key(); } + + void set_deleted_key(const key_type& key) { rep.set_deleted_key(key); } + void clear_deleted_key() { rep.clear_deleted_key(); } + key_type deleted_key() const { return rep.deleted_key(); } + + // These are standard + size_type erase(const key_type& key) { return rep.erase(key); } + void erase(iterator it) { rep.erase(it); } + void erase(iterator f, iterator l) { rep.erase(f, l); } + + + // Comparison + bool operator==(const dense_hash_set& hs) const { return rep == hs.rep; } + bool operator!=(const dense_hash_set& hs) const { return rep != hs.rep; } + + + // I/O -- this is an add-on for writing metainformation to disk + bool write_metadata(FILE *fp) { return rep.write_metadata(fp); } + bool read_metadata(FILE *fp) { return rep.read_metadata(fp); } + bool write_nopointer_data(FILE *fp) { return rep.write_nopointer_data(fp); } + bool read_nopointer_data(FILE *fp) { return rep.read_nopointer_data(fp); } +}; + +template +inline void swap(dense_hash_set& hs1, + dense_hash_set& hs2) { + hs1.swap(hs2); +} + +_END_GOOGLE_NAMESPACE_ + +#endif /* _DENSE_HASH_SET_H_ */ diff --git a/external/google/sparse_hash_map b/external/google/sparse_hash_map new file mode 100644 index 00000000..07e52d8b --- /dev/null +++ b/external/google/sparse_hash_map @@ -0,0 +1,310 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// --- +// Author: Craig Silverstein +// +// This is just a very thin wrapper over sparsehashtable.h, just +// like sgi stl's stl_hash_map is a very thin wrapper over +// stl_hashtable. The major thing we define is operator[], because +// we have a concept of a data_type which stl_hashtable doesn't +// (it only has a key and a value). +// +// We adhere mostly to the STL semantics for hash-map. One important +// exception is that insert() may invalidate iterators entirely -- STL +// semantics are that insert() may reorder iterators, but they all +// still refer to something valid in the hashtable. Not so for us. +// Likewise, insert() may invalidate pointers into the hashtable. +// (Whether insert invalidates iterators and pointers depends on +// whether it results in a hashtable resize). On the plus side, +// delete() doesn't invalidate iterators or pointers at all, or even +// change the ordering of elements. +// +// Here are a few "power user" tips: +// +// 1) set_deleted_key(): +// Unlike STL's hash_map, if you want to use erase() you +// *must* call set_deleted_key() after construction. +// +// 2) resize(0): +// When an item is deleted, its memory isn't freed right +// away. This is what allows you to iterate over a hashtable +// and call erase() without invalidating the iterator. +// To force the memory to be freed, call resize(0). +// For tr1 compatibility, this can also be called as rehash(0). +// +// 3) min_load_factor(0.0) +// Setting the minimum load factor to 0.0 guarantees that +// the hash table will never shrink. +// +// Roughly speaking: +// (1) dense_hash_map: fastest, uses the most memory unless entries are small +// (2) sparse_hash_map: slowest, uses the least memory +// (3) hash_map / unordered_map (STL): in the middle +// +// Typically I use sparse_hash_map when I care about space and/or when +// I need to save the hashtable on disk. I use hash_map otherwise. I +// don't personally use dense_hash_map ever; some people use it for +// small maps with lots of lookups. +// +// - dense_hash_map has, typically, about 78% memory overhead (if your +// data takes up X bytes, the hash_map uses .78X more bytes in overhead). +// - sparse_hash_map has about 4 bits overhead per entry. +// - sparse_hash_map can be 3-7 times slower than the others for lookup and, +// especially, inserts. See time_hash_map.cc for details. +// +// See /usr/(local/)?doc/sparsehash-*/sparse_hash_map.html +// for information about how to use this class. + +#ifndef _SPARSE_HASH_MAP_H_ +#define _SPARSE_HASH_MAP_H_ + +#include +#include // for FILE * in read()/write() +#include // for the default template args +#include // for equal_to +#include // for alloc<> +#include // for pair<> +#include HASH_FUN_H // defined in config.h +#include +#include + + +_START_GOOGLE_NAMESPACE_ + +using STL_NAMESPACE::pair; + +template , // defined in sparseconfig.h + class EqualKey = STL_NAMESPACE::equal_to, + class Alloc = libc_allocator_with_realloc > > +class sparse_hash_map { + private: + // Apparently select1st is not stl-standard, so we define our own + struct SelectKey { + typedef const Key& result_type; + const Key& operator()(const pair& p) const { + return p.first; + } + }; + struct SetKey { + void operator()(pair* value, const Key& new_key) const { + *const_cast(&value->first) = new_key; + // It would be nice to clear the rest of value here as well, in + // case it's taking up a lot of memory. We do this by clearing + // the value. This assumes T has a zero-arg constructor! + value->second = T(); + } + }; + // For operator[]. + struct DefaultValue { + STL_NAMESPACE::pair operator()(const Key& key) { + return STL_NAMESPACE::make_pair(key, T()); + } + }; + + // The actual data + typedef sparse_hashtable, Key, HashFcn, SelectKey, + SetKey, EqualKey, Alloc> ht; + ht rep; + + public: + typedef typename ht::key_type key_type; + typedef T data_type; + typedef T mapped_type; + typedef typename ht::value_type value_type; + typedef typename ht::hasher hasher; + typedef typename ht::key_equal key_equal; + typedef Alloc allocator_type; + + typedef typename ht::size_type size_type; + typedef typename ht::difference_type difference_type; + typedef typename ht::pointer pointer; + typedef typename ht::const_pointer const_pointer; + typedef typename ht::reference reference; + typedef typename ht::const_reference const_reference; + + typedef typename ht::iterator iterator; + typedef typename ht::const_iterator const_iterator; + typedef typename ht::local_iterator local_iterator; + typedef typename ht::const_local_iterator const_local_iterator; + + // Iterator functions + iterator begin() { return rep.begin(); } + iterator end() { return rep.end(); } + const_iterator begin() const { return rep.begin(); } + const_iterator end() const { return rep.end(); } + + // These come from tr1's unordered_map. For us, a bucket has 0 or 1 elements. + local_iterator begin(size_type i) { return rep.begin(i); } + local_iterator end(size_type i) { return rep.end(i); } + const_local_iterator begin(size_type i) const { return rep.begin(i); } + const_local_iterator end(size_type i) const { return rep.end(i); } + + // Accessor functions + allocator_type get_allocator() const { return rep.get_allocator(); } + hasher hash_funct() const { return rep.hash_funct(); } + hasher hash_function() const { return hash_funct(); } + key_equal key_eq() const { return rep.key_eq(); } + + + // Constructors + explicit sparse_hash_map(size_type expected_max_items_in_table = 0, + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& alloc = allocator_type()) + : rep(expected_max_items_in_table, hf, eql, SelectKey(), SetKey(), alloc) { + } + + template + sparse_hash_map(InputIterator f, InputIterator l, + size_type expected_max_items_in_table = 0, + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& alloc = allocator_type()) + : rep(expected_max_items_in_table, hf, eql, SelectKey(), SetKey(), alloc) { + rep.insert(f, l); + } + // We use the default copy constructor + // We use the default operator=() + // We use the default destructor + + void clear() { rep.clear(); } + void swap(sparse_hash_map& hs) { rep.swap(hs.rep); } + + + // Functions concerning size + size_type size() const { return rep.size(); } + size_type max_size() const { return rep.max_size(); } + bool empty() const { return rep.empty(); } + size_type bucket_count() const { return rep.bucket_count(); } + size_type max_bucket_count() const { return rep.max_bucket_count(); } + + // These are tr1 methods. bucket() is the bucket the key is or would be in. + size_type bucket_size(size_type i) const { return rep.bucket_size(i); } + size_type bucket(const key_type& key) const { return rep.bucket(key); } + float load_factor() const { + return size() * 1.0f / bucket_count(); + } + float max_load_factor() const { + float shrink, grow; + rep.get_resizing_parameters(&shrink, &grow); + return grow; + } + void max_load_factor(float new_grow) { + float shrink, grow; + rep.get_resizing_parameters(&shrink, &grow); + rep.set_resizing_parameters(shrink, new_grow); + } + // These aren't tr1 methods but perhaps ought to be. + float min_load_factor() const { + float shrink, grow; + rep.get_resizing_parameters(&shrink, &grow); + return shrink; + } + void min_load_factor(float new_shrink) { + float shrink, grow; + rep.get_resizing_parameters(&shrink, &grow); + rep.set_resizing_parameters(new_shrink, grow); + } + // Deprecated; use min_load_factor() or max_load_factor() instead. + void set_resizing_parameters(float shrink, float grow) { + rep.set_resizing_parameters(shrink, grow); + } + + void resize(size_type hint) { rep.resize(hint); } + void rehash(size_type hint) { resize(hint); } // the tr1 name + + // Lookup routines + iterator find(const key_type& key) { return rep.find(key); } + const_iterator find(const key_type& key) const { return rep.find(key); } + + data_type& operator[](const key_type& key) { // This is our value-add! + // If key is in the hashtable, returns find(key)->second, + // otherwise returns insert(value_type(key, T()).first->second. + // Note it does not create an empty T unless the find fails. + return rep.template find_or_insert(key).second; + } + + size_type count(const key_type& key) const { return rep.count(key); } + + pair equal_range(const key_type& key) { + return rep.equal_range(key); + } + pair equal_range(const key_type& key) const { + return rep.equal_range(key); + } + + // Insertion routines + pair insert(const value_type& obj) { return rep.insert(obj); } + template + void insert(InputIterator f, InputIterator l) { rep.insert(f, l); } + void insert(const_iterator f, const_iterator l) { rep.insert(f, l); } + // required for std::insert_iterator; the passed-in iterator is ignored + iterator insert(iterator, const value_type& obj) { return insert(obj).first; } + + + // Deletion routines + // THESE ARE NON-STANDARD! I make you specify an "impossible" key + // value to identify deleted buckets. You can change the key as + // time goes on, or get rid of it entirely to be insert-only. + void set_deleted_key(const key_type& key) { + rep.set_deleted_key(key); + } + void clear_deleted_key() { rep.clear_deleted_key(); } + key_type deleted_key() const { return rep.deleted_key(); } + + // These are standard + size_type erase(const key_type& key) { return rep.erase(key); } + void erase(iterator it) { rep.erase(it); } + void erase(iterator f, iterator l) { rep.erase(f, l); } + + + // Comparison + bool operator==(const sparse_hash_map& hs) const { return rep == hs.rep; } + bool operator!=(const sparse_hash_map& hs) const { return rep != hs.rep; } + + + // I/O -- this is an add-on for writing metainformation to disk + bool write_metadata(FILE *fp) { return rep.write_metadata(fp); } + bool read_metadata(FILE *fp) { return rep.read_metadata(fp); } + bool write_nopointer_data(FILE *fp) { return rep.write_nopointer_data(fp); } + bool read_nopointer_data(FILE *fp) { return rep.read_nopointer_data(fp); } +}; + +// We need a global swap as well +template +inline void swap(sparse_hash_map& hm1, + sparse_hash_map& hm2) { + hm1.swap(hm2); +} + +_END_GOOGLE_NAMESPACE_ + +#endif /* _SPARSE_HASH_MAP_H_ */ diff --git a/external/google/sparse_hash_set b/external/google/sparse_hash_set new file mode 100644 index 00000000..6be16e9a --- /dev/null +++ b/external/google/sparse_hash_set @@ -0,0 +1,285 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// --- +// Author: Craig Silverstein +// +// This is just a very thin wrapper over sparsehashtable.h, just +// like sgi stl's stl_hash_set is a very thin wrapper over +// stl_hashtable. The major thing we define is operator[], because +// we have a concept of a data_type which stl_hashtable doesn't +// (it only has a key and a value). +// +// This is more different from sparse_hash_map than you might think, +// because all iterators for sets are const (you obviously can't +// change the key, and for sets there is no value). +// +// We adhere mostly to the STL semantics for hash-map. One important +// exception is that insert() may invalidate iterators entirely -- STL +// semantics are that insert() may reorder iterators, but they all +// still refer to something valid in the hashtable. Not so for us. +// Likewise, insert() may invalidate pointers into the hashtable. +// (Whether insert invalidates iterators and pointers depends on +// whether it results in a hashtable resize). On the plus side, +// delete() doesn't invalidate iterators or pointers at all, or even +// change the ordering of elements. +// +// Here are a few "power user" tips: +// +// 1) set_deleted_key(): +// Unlike STL's hash_map, if you want to use erase() you +// *must* call set_deleted_key() after construction. +// +// 2) resize(0): +// When an item is deleted, its memory isn't freed right +// away. This allows you to iterate over a hashtable, +// and call erase(), without invalidating the iterator. +// To force the memory to be freed, call resize(0). +// For tr1 compatibility, this can also be called as rehash(0). +// +// 3) min_load_factor(0.0) +// Setting the minimum load factor to 0.0 guarantees that +// the hash table will never shrink. +// +// Roughly speaking: +// (1) dense_hash_set: fastest, uses the most memory unless entries are small +// (2) sparse_hash_set: slowest, uses the least memory +// (3) hash_set / unordered_set (STL): in the middle +// +// Typically I use sparse_hash_set when I care about space and/or when +// I need to save the hashtable on disk. I use hash_set otherwise. I +// don't personally use dense_hash_set ever; some people use it for +// small sets with lots of lookups. +// +// - dense_hash_set has, typically, about 78% memory overhead (if your +// data takes up X bytes, the hash_set uses .78X more bytes in overhead). +// - sparse_hash_set has about 4 bits overhead per entry. +// - sparse_hash_set can be 3-7 times slower than the others for lookup and, +// especially, inserts. See time_hash_map.cc for details. +// +// See /usr/(local/)?doc/sparsehash-*/sparse_hash_set.html +// for information about how to use this class. + +#ifndef _SPARSE_HASH_SET_H_ +#define _SPARSE_HASH_SET_H_ + +#include +#include // for FILE * in read()/write() +#include // for the default template args +#include // for equal_to +#include // for alloc<> +#include // for pair<> +#include HASH_FUN_H // defined in config.h +#include +#include + +_START_GOOGLE_NAMESPACE_ + +using STL_NAMESPACE::pair; + +template , // defined in sparseconfig.h + class EqualKey = STL_NAMESPACE::equal_to, + class Alloc = libc_allocator_with_realloc > +class sparse_hash_set { + private: + // Apparently identity is not stl-standard, so we define our own + struct Identity { + typedef const Value& result_type; + const Value& operator()(const Value& v) const { return v; } + }; + struct SetKey { + void operator()(Value* value, const Value& new_key) const { + *value = new_key; + } + }; + + typedef sparse_hashtable ht; + ht rep; + + public: + typedef typename ht::key_type key_type; + typedef typename ht::value_type value_type; + typedef typename ht::hasher hasher; + typedef typename ht::key_equal key_equal; + typedef Alloc allocator_type; + + typedef typename ht::size_type size_type; + typedef typename ht::difference_type difference_type; + typedef typename ht::const_pointer pointer; + typedef typename ht::const_pointer const_pointer; + typedef typename ht::const_reference reference; + typedef typename ht::const_reference const_reference; + + typedef typename ht::const_iterator iterator; + typedef typename ht::const_iterator const_iterator; + typedef typename ht::const_local_iterator local_iterator; + typedef typename ht::const_local_iterator const_local_iterator; + + + // Iterator functions -- recall all iterators are const + iterator begin() const { return rep.begin(); } + iterator end() const { return rep.end(); } + + // These come from tr1's unordered_set. For us, a bucket has 0 or 1 elements. + local_iterator begin(size_type i) const { return rep.begin(i); } + local_iterator end(size_type i) const { return rep.end(i); } + + + // Accessor functions + allocator_type get_allocator() const { return rep.get_allocator(); } + hasher hash_funct() const { return rep.hash_funct(); } + hasher hash_function() const { return hash_funct(); } // tr1 name + key_equal key_eq() const { return rep.key_eq(); } + + + // Constructors + explicit sparse_hash_set(size_type expected_max_items_in_table = 0, + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& alloc = allocator_type()) + : rep(expected_max_items_in_table, hf, eql, Identity(), SetKey(), alloc) { + } + + template + sparse_hash_set(InputIterator f, InputIterator l, + size_type expected_max_items_in_table = 0, + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& alloc = allocator_type()) + : rep(expected_max_items_in_table, hf, eql, Identity(), SetKey(), alloc) { + rep.insert(f, l); + } + // We use the default copy constructor + // We use the default operator=() + // We use the default destructor + + void clear() { rep.clear(); } + void swap(sparse_hash_set& hs) { rep.swap(hs.rep); } + + + // Functions concerning size + size_type size() const { return rep.size(); } + size_type max_size() const { return rep.max_size(); } + bool empty() const { return rep.empty(); } + size_type bucket_count() const { return rep.bucket_count(); } + size_type max_bucket_count() const { return rep.max_bucket_count(); } + + // These are tr1 methods. bucket() is the bucket the key is or would be in. + size_type bucket_size(size_type i) const { return rep.bucket_size(i); } + size_type bucket(const key_type& key) const { return rep.bucket(key); } + float load_factor() const { + return size() * 1.0f / bucket_count(); + } + float max_load_factor() const { + float shrink, grow; + rep.get_resizing_parameters(&shrink, &grow); + return grow; + } + void max_load_factor(float new_grow) { + float shrink, grow; + rep.get_resizing_parameters(&shrink, &grow); + rep.set_resizing_parameters(shrink, new_grow); + } + // These aren't tr1 methods but perhaps ought to be. + float min_load_factor() const { + float shrink, grow; + rep.get_resizing_parameters(&shrink, &grow); + return shrink; + } + void min_load_factor(float new_shrink) { + float shrink, grow; + rep.get_resizing_parameters(&shrink, &grow); + rep.set_resizing_parameters(new_shrink, grow); + } + // Deprecated; use min_load_factor() or max_load_factor() instead. + void set_resizing_parameters(float shrink, float grow) { + rep.set_resizing_parameters(shrink, grow); + } + + void resize(size_type hint) { rep.resize(hint); } + void rehash(size_type hint) { resize(hint); } // the tr1 name + + // Lookup routines + iterator find(const key_type& key) const { return rep.find(key); } + + size_type count(const key_type& key) const { return rep.count(key); } + + pair equal_range(const key_type& key) const { + return rep.equal_range(key); + } + + // Insertion routines + pair insert(const value_type& obj) { + pair p = rep.insert(obj); + return pair(p.first, p.second); // const to non-const + } + template + void insert(InputIterator f, InputIterator l) { rep.insert(f, l); } + void insert(const_iterator f, const_iterator l) { rep.insert(f, l); } + // required for std::insert_iterator; the passed-in iterator is ignored + iterator insert(iterator, const value_type& obj) { return insert(obj).first; } + + + // Deletion routines + // THESE ARE NON-STANDARD! I make you specify an "impossible" key + // value to identify deleted buckets. You can change the key as + // time goes on, or get rid of it entirely to be insert-only. + void set_deleted_key(const key_type& key) { rep.set_deleted_key(key); } + void clear_deleted_key() { rep.clear_deleted_key(); } + key_type deleted_key() const { return rep.deleted_key(); } + + // These are standard + size_type erase(const key_type& key) { return rep.erase(key); } + void erase(iterator it) { rep.erase(it); } + void erase(iterator f, iterator l) { rep.erase(f, l); } + + + // Comparison + bool operator==(const sparse_hash_set& hs) const { return rep == hs.rep; } + bool operator!=(const sparse_hash_set& hs) const { return rep != hs.rep; } + + + // I/O -- this is an add-on for writing metainformation to disk + bool write_metadata(FILE *fp) { return rep.write_metadata(fp); } + bool read_metadata(FILE *fp) { return rep.read_metadata(fp); } + bool write_nopointer_data(FILE *fp) { return rep.write_nopointer_data(fp); } + bool read_nopointer_data(FILE *fp) { return rep.read_nopointer_data(fp); } +}; + +template +inline void swap(sparse_hash_set& hs1, + sparse_hash_set& hs2) { + hs1.swap(hs2); +} + +_END_GOOGLE_NAMESPACE_ + +#endif /* _SPARSE_HASH_SET_H_ */ diff --git a/external/google/sparsehash/densehashtable.h b/external/google/sparsehash/densehashtable.h new file mode 100644 index 00000000..8a3126e3 --- /dev/null +++ b/external/google/sparsehash/densehashtable.h @@ -0,0 +1,1268 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// --- +// Author: Craig Silverstein +// +// A dense hashtable is a particular implementation of +// a hashtable: one that is meant to minimize memory allocation. +// It does this by using an array to store all the data. We +// steal a value from the key space to indicate "empty" array +// elements (ie indices where no item lives) and another to indicate +// "deleted" elements. +// +// (Note it is possible to change the value of the delete key +// on the fly; you can even remove it, though after that point +// the hashtable is insert_only until you set it again. The empty +// value however can't be changed.) +// +// To minimize allocation and pointer overhead, we use internal +// probing, in which the hashtable is a single table, and collisions +// are resolved by trying to insert again in another bucket. The +// most cache-efficient internal probing schemes are linear probing +// (which suffers, alas, from clumping) and quadratic probing, which +// is what we implement by default. +// +// Type requirements: value_type is required to be Copy Constructible +// and Default Constructible. It is not required to be (and commonly +// isn't) Assignable. +// +// You probably shouldn't use this code directly. Use +// or instead. + +// You can change the following below: +// HT_OCCUPANCY_PCT -- how full before we double size +// HT_EMPTY_PCT -- how empty before we halve size +// HT_MIN_BUCKETS -- default smallest bucket size +// +// You can also change enlarge_factor (which defaults to +// HT_OCCUPANCY_PCT), and shrink_factor (which defaults to +// HT_EMPTY_PCT) with set_resizing_parameters(). +// +// How to decide what values to use? +// shrink_factor's default of .4 * OCCUPANCY_PCT, is probably good. +// HT_MIN_BUCKETS is probably unnecessary since you can specify +// (indirectly) the starting number of buckets at construct-time. +// For enlarge_factor, you can use this chart to try to trade-off +// expected lookup time to the space taken up. By default, this +// code uses quadratic probing, though you can change it to linear +// via _JUMP below if you really want to. +// +// From http://www.augustana.ca/~mohrj/courses/1999.fall/csc210/lecture_notes/hashing.html +// NUMBER OF PROBES / LOOKUP Successful Unsuccessful +// Quadratic collision resolution 1 - ln(1-L) - L/2 1/(1-L) - L - ln(1-L) +// Linear collision resolution [1+1/(1-L)]/2 [1+1/(1-L)2]/2 +// +// -- enlarge_factor -- 0.10 0.50 0.60 0.75 0.80 0.90 0.99 +// QUADRATIC COLLISION RES. +// probes/successful lookup 1.05 1.44 1.62 2.01 2.21 2.85 5.11 +// probes/unsuccessful lookup 1.11 2.19 2.82 4.64 5.81 11.4 103.6 +// LINEAR COLLISION RES. +// probes/successful lookup 1.06 1.5 1.75 2.5 3.0 5.5 50.5 +// probes/unsuccessful lookup 1.12 2.5 3.6 8.5 13.0 50.0 5000.0 + +#ifndef _DENSEHASHTABLE_H_ +#define _DENSEHASHTABLE_H_ + +// The probing method +// Linear probing +// #define JUMP_(key, num_probes) ( 1 ) +// Quadratic probing +#define JUMP_(key, num_probes) ( num_probes ) + + +#include +#include +#include +#include // for abort() +#include // For swap(), eg +#include // For length_error +#include // For cerr +#include // For uninitialized_fill, uninitialized_copy +#include // for pair<> +#include // for facts about iterator tags +#include // for numeric_limits<> +#include +#include +#include // for true_type, integral_constant, etc. + +_START_GOOGLE_NAMESPACE_ + +using STL_NAMESPACE::pair; + +// Hashtable class, used to implement the hashed associative containers +// hash_set and hash_map. + +// Value: what is stored in the table (each bucket is a Value). +// Key: something in a 1-to-1 correspondence to a Value, that can be used +// to search for a Value in the table (find() takes a Key). +// HashFcn: Takes a Key and returns an integer, the more unique the better. +// ExtractKey: given a Value, returns the unique Key associated with it. +// Must inherit from unary_function, or at least have a +// result_type enum indicating the return type of operator(). +// SetKey: given a Value* and a Key, modifies the value such that +// ExtractKey(value) == key. We guarantee this is only called +// with key == deleted_key or key == empty_key. +// EqualKey: Given two Keys, says whether they are the same (that is, +// if they are both associated with the same Value). +// Alloc: STL allocator to use to allocate memory. + +template +class dense_hashtable; + +template +struct dense_hashtable_iterator; + +template +struct dense_hashtable_const_iterator; + +// We're just an array, but we need to skip over empty and deleted elements +template +struct dense_hashtable_iterator { + private: + typedef typename A::template rebind::other value_alloc_type; + + public: + typedef dense_hashtable_iterator iterator; + typedef dense_hashtable_const_iterator const_iterator; + + typedef STL_NAMESPACE::forward_iterator_tag iterator_category; + typedef V value_type; + typedef typename value_alloc_type::difference_type difference_type; + typedef typename value_alloc_type::size_type size_type; + typedef typename value_alloc_type::reference reference; + typedef typename value_alloc_type::pointer pointer; + + // "Real" constructor and default constructor + dense_hashtable_iterator(const dense_hashtable *h, + pointer it, pointer it_end, bool advance) + : ht(h), pos(it), end(it_end) { + if (advance) advance_past_empty_and_deleted(); + } + dense_hashtable_iterator() { } + // The default destructor is fine; we don't define one + // The default operator= is fine; we don't define one + + // Happy dereferencer + reference operator*() const { return *pos; } + pointer operator->() const { return &(operator*()); } + + // Arithmetic. The only hard part is making sure that + // we're not on an empty or marked-deleted array element + void advance_past_empty_and_deleted() { + while ( pos != end && (ht->test_empty(*this) || ht->test_deleted(*this)) ) + ++pos; + } + iterator& operator++() { + assert(pos != end); ++pos; advance_past_empty_and_deleted(); return *this; + } + iterator operator++(int) { iterator tmp(*this); ++*this; return tmp; } + + // Comparison. + bool operator==(const iterator& it) const { return pos == it.pos; } + bool operator!=(const iterator& it) const { return pos != it.pos; } + + + // The actual data + const dense_hashtable *ht; + pointer pos, end; +}; + + +// Now do it all again, but with const-ness! +template +struct dense_hashtable_const_iterator { + private: + typedef typename A::template rebind::other value_alloc_type; + + public: + typedef dense_hashtable_iterator iterator; + typedef dense_hashtable_const_iterator const_iterator; + + typedef STL_NAMESPACE::forward_iterator_tag iterator_category; + typedef V value_type; + typedef typename value_alloc_type::difference_type difference_type; + typedef typename value_alloc_type::size_type size_type; + typedef typename value_alloc_type::const_reference reference; + typedef typename value_alloc_type::const_pointer pointer; + + // "Real" constructor and default constructor + dense_hashtable_const_iterator( + const dense_hashtable *h, + pointer it, pointer it_end, bool advance) + : ht(h), pos(it), end(it_end) { + if (advance) advance_past_empty_and_deleted(); + } + dense_hashtable_const_iterator() + : ht(NULL), pos(pointer()), end(pointer()) { } + // This lets us convert regular iterators to const iterators + dense_hashtable_const_iterator(const iterator &it) + : ht(it.ht), pos(it.pos), end(it.end) { } + // The default destructor is fine; we don't define one + // The default operator= is fine; we don't define one + + // Happy dereferencer + reference operator*() const { return *pos; } + pointer operator->() const { return &(operator*()); } + + // Arithmetic. The only hard part is making sure that + // we're not on an empty or marked-deleted array element + void advance_past_empty_and_deleted() { + while ( pos != end && (ht->test_empty(*this) || ht->test_deleted(*this)) ) + ++pos; + } + const_iterator& operator++() { + assert(pos != end); ++pos; advance_past_empty_and_deleted(); return *this; + } + const_iterator operator++(int) { const_iterator tmp(*this); ++*this; return tmp; } + + // Comparison. + bool operator==(const const_iterator& it) const { return pos == it.pos; } + bool operator!=(const const_iterator& it) const { return pos != it.pos; } + + + // The actual data + const dense_hashtable *ht; + pointer pos, end; +}; + +template +class dense_hashtable { + private: + typedef typename Alloc::template rebind::other value_alloc_type; + + public: + typedef Key key_type; + typedef Value value_type; + typedef HashFcn hasher; + typedef EqualKey key_equal; + typedef Alloc allocator_type; + + typedef typename value_alloc_type::size_type size_type; + typedef typename value_alloc_type::difference_type difference_type; + typedef typename value_alloc_type::reference reference; + typedef typename value_alloc_type::const_reference const_reference; + typedef typename value_alloc_type::pointer pointer; + typedef typename value_alloc_type::const_pointer const_pointer; + typedef dense_hashtable_iterator + iterator; + + typedef dense_hashtable_const_iterator + const_iterator; + + // These come from tr1. For us they're the same as regular iterators. + typedef iterator local_iterator; + typedef const_iterator const_local_iterator; + + // How full we let the table get before we resize, by default. + // Knuth says .8 is good -- higher causes us to probe too much, + // though it saves memory. + static const int HT_OCCUPANCY_PCT; // = 50 (out of 100) + + // How empty we let the table get before we resize lower, by default. + // (0.0 means never resize lower.) + // It should be less than OCCUPANCY_PCT / 2 or we thrash resizing + static const int HT_EMPTY_PCT; // = 0.4 * HT_OCCUPANCY_PCT; + + // Minimum size we're willing to let hashtables be. + // Must be a power of two, and at least 4. + // Note, however, that for a given hashtable, the initial size is a + // function of the first constructor arg, and may be >HT_MIN_BUCKETS. + static const size_type HT_MIN_BUCKETS = 4; + + // By default, if you don't specify a hashtable size at + // construction-time, we use this size. Must be a power of two, and + // at least HT_MIN_BUCKETS. + static const size_type HT_DEFAULT_STARTING_BUCKETS = 32; + + // ITERATOR FUNCTIONS + iterator begin() { return iterator(this, table, + table + num_buckets, true); } + iterator end() { return iterator(this, table + num_buckets, + table + num_buckets, true); } + const_iterator begin() const { return const_iterator(this, table, + table+num_buckets,true);} + const_iterator end() const { return const_iterator(this, table + num_buckets, + table+num_buckets,true);} + + // These come from tr1 unordered_map. They iterate over 'bucket' n. + // We'll just consider bucket n to be the n-th element of the table. + local_iterator begin(size_type i) { + return local_iterator(this, table + i, table + i+1, false); + } + local_iterator end(size_type i) { + local_iterator it = begin(i); + if (!test_empty(i) && !test_deleted(i)) + ++it; + return it; + } + const_local_iterator begin(size_type i) const { + return const_local_iterator(this, table + i, table + i+1, false); + } + const_local_iterator end(size_type i) const { + const_local_iterator it = begin(i); + if (!test_empty(i) && !test_deleted(i)) + ++it; + return it; + } + + // ACCESSOR FUNCTIONS for the things we templatize on, basically + hasher hash_funct() const { return settings; } + key_equal key_eq() const { return key_info; } + allocator_type get_allocator() const { + return allocator_type(val_info); + } + + // Accessor function for statistics gathering. + int num_table_copies() const { return settings.num_ht_copies(); } + + private: + // Annoyingly, we can't copy values around, because they might have + // const components (they're probably pair). We use + // explicit destructor invocation and placement new to get around + // this. Arg. + void set_value(pointer dst, const_reference src) { + dst->~value_type(); // delete the old value, if any + new(dst) value_type(src); + } + + void destroy_buckets(size_type first, size_type last) { + for ( ; first != last; ++first) + table[first].~value_type(); + } + + // DELETE HELPER FUNCTIONS + // This lets the user describe a key that will indicate deleted + // table entries. This key should be an "impossible" entry -- + // if you try to insert it for real, you won't be able to retrieve it! + // (NB: while you pass in an entire value, only the key part is looked + // at. This is just because I don't know how to assign just a key.) + private: + void squash_deleted() { // gets rid of any deleted entries we have + if ( num_deleted ) { // get rid of deleted before writing + dense_hashtable tmp(*this); // copying will get rid of deleted + swap(tmp); // now we are tmp + } + assert(num_deleted == 0); + } + + bool test_deleted_key(const key_type& key) const { + // The num_deleted test is crucial for read(): after read(), the ht values + // are garbage, and we don't want to think some of them are deleted. + // Invariant: !use_deleted implies num_deleted is 0. + assert(settings.use_deleted() || num_deleted == 0); + return num_deleted > 0 && equals(key_info.delkey, key); + } + + public: + void set_deleted_key(const key_type &key) { + // the empty indicator (if specified) and the deleted indicator + // must be different + assert((!settings.use_empty() || !equals(key, get_key(val_info.emptyval))) + && "Passed the empty-key to set_deleted_key"); + // It's only safe to change what "deleted" means if we purge deleted guys + squash_deleted(); + settings.set_use_deleted(true); + key_info.delkey = key; + } + void clear_deleted_key() { + squash_deleted(); + settings.set_use_deleted(false); + } + key_type deleted_key() const { + assert(settings.use_deleted() + && "Must set deleted key before calling deleted_key"); + return key_info.delkey; + } + + // These are public so the iterators can use them + // True if the item at position bucknum is "deleted" marker + bool test_deleted(size_type bucknum) const { + return test_deleted_key(get_key(table[bucknum])); + } + bool test_deleted(const iterator &it) const { + return test_deleted_key(get_key(*it)); + } + bool test_deleted(const const_iterator &it) const { + return test_deleted_key(get_key(*it)); + } + + private: + // Set it so test_deleted is true. true if object didn't used to be deleted. + bool set_deleted(iterator &it) { + assert(settings.use_deleted()); + bool retval = !test_deleted(it); + // &* converts from iterator to value-type. + set_key(&(*it), key_info.delkey); + return retval; + } + // Set it so test_deleted is false. true if object used to be deleted. + bool clear_deleted(iterator &it) { + assert(settings.use_deleted()); + // Happens automatically when we assign something else in its place. + return test_deleted(it); + } + + // We also allow to set/clear the deleted bit on a const iterator. + // We allow a const_iterator for the same reason you can delete a + // const pointer: it's convenient, and semantically you can't use + // 'it' after it's been deleted anyway, so its const-ness doesn't + // really matter. + bool set_deleted(const_iterator &it) { + assert(settings.use_deleted()); + bool retval = !test_deleted(it); + set_key(const_cast(&(*it)), key_info.delkey); + return retval; + } + // Set it so test_deleted is false. true if object used to be deleted. + bool clear_deleted(const_iterator &it) { + assert(settings.use_deleted()); + return test_deleted(it); + } + + // EMPTY HELPER FUNCTIONS + // This lets the user describe a key that will indicate empty (unused) + // table entries. This key should be an "impossible" entry -- + // if you try to insert it for real, you won't be able to retrieve it! + // (NB: while you pass in an entire value, only the key part is looked + // at. This is just because I don't know how to assign just a key.) + public: + // These are public so the iterators can use them + // True if the item at position bucknum is "empty" marker + bool test_empty(size_type bucknum) const { + assert(settings.use_empty()); // we always need to know what's empty! + return equals(get_key(val_info.emptyval), get_key(table[bucknum])); + } + bool test_empty(const iterator &it) const { + assert(settings.use_empty()); // we always need to know what's empty! + return equals(get_key(val_info.emptyval), get_key(*it)); + } + bool test_empty(const const_iterator &it) const { + assert(settings.use_empty()); // we always need to know what's empty! + return equals(get_key(val_info.emptyval), get_key(*it)); + } + + private: + void fill_range_with_empty(pointer table_start, pointer table_end) { + STL_NAMESPACE::uninitialized_fill(table_start, table_end, val_info.emptyval); + } + + public: + // TODO(csilvers): change all callers of this to pass in a key instead, + // and take a const key_type instead of const value_type. + void set_empty_key(const_reference val) { + // Once you set the empty key, you can't change it + assert(!settings.use_empty() && "Calling set_empty_key multiple times"); + // The deleted indicator (if specified) and the empty indicator + // must be different. + assert((!settings.use_deleted() || !equals(get_key(val), key_info.delkey)) + && "Setting the empty key the same as the deleted key"); + settings.set_use_empty(true); + set_value(&val_info.emptyval, val); + + assert(!table); // must set before first use + // num_buckets was set in constructor even though table was NULL + table = val_info.allocate(num_buckets); + assert(table); + fill_range_with_empty(table, table + num_buckets); + } + // TODO(sjackman): return a key_type rather than a value_type + value_type empty_key() const { + assert(settings.use_empty()); + return val_info.emptyval; + } + + // FUNCTIONS CONCERNING SIZE + public: + size_type size() const { return num_elements - num_deleted; } + size_type max_size() const { return val_info.max_size(); } + bool empty() const { return size() == 0; } + size_type bucket_count() const { return num_buckets; } + size_type max_bucket_count() const { return max_size(); } + size_type nonempty_bucket_count() const { return num_elements; } + // These are tr1 methods. Their idea of 'bucket' doesn't map well to + // what we do. We just say every bucket has 0 or 1 items in it. + size_type bucket_size(size_type i) const { + return begin(i) == end(i) ? 0 : 1; + } + + private: + // Because of the above, size_type(-1) is never legal; use it for errors + static const size_type ILLEGAL_BUCKET = size_type(-1); + + // Used after a string of deletes. Returns true if we actually shrunk. + // TODO(csilvers): take a delta so we can take into account inserts + // done after shrinking. Maybe make part of the Settings class? + bool maybe_shrink() { + assert(num_elements >= num_deleted); + assert((bucket_count() & (bucket_count()-1)) == 0); // is a power of two + assert(bucket_count() >= HT_MIN_BUCKETS); + bool retval = false; + + // If you construct a hashtable with < HT_DEFAULT_STARTING_BUCKETS, + // we'll never shrink until you get relatively big, and we'll never + // shrink below HT_DEFAULT_STARTING_BUCKETS. Otherwise, something + // like "dense_hash_set x; x.insert(4); x.erase(4);" will + // shrink us down to HT_MIN_BUCKETS buckets, which is too small. + const size_type num_remain = num_elements - num_deleted; + const size_type shrink_threshold = settings.shrink_threshold(); + if (shrink_threshold > 0 && num_remain < shrink_threshold && + bucket_count() > HT_DEFAULT_STARTING_BUCKETS) { + const float shrink_factor = settings.shrink_factor(); + size_type sz = bucket_count() / 2; // find how much we should shrink + while (sz > HT_DEFAULT_STARTING_BUCKETS && + num_remain < sz * shrink_factor) { + sz /= 2; // stay a power of 2 + } + dense_hashtable tmp(*this, sz); // Do the actual resizing + swap(tmp); // now we are tmp + retval = true; + } + settings.set_consider_shrink(false); // because we just considered it + return retval; + } + + // We'll let you resize a hashtable -- though this makes us copy all! + // When you resize, you say, "make it big enough for this many more elements" + // Returns true if we actually resized, false if size was already ok. + bool resize_delta(size_type delta) { + bool did_resize = false; + if ( settings.consider_shrink() ) { // see if lots of deletes happened + if ( maybe_shrink() ) + did_resize = true; + } + if (num_elements >= (STL_NAMESPACE::numeric_limits::max)() - delta) + throw std::length_error("resize overflow"); + if ( bucket_count() >= HT_MIN_BUCKETS && + (num_elements + delta) <= settings.enlarge_threshold() ) + return did_resize; // we're ok as we are + + // Sometimes, we need to resize just to get rid of all the + // "deleted" buckets that are clogging up the hashtable. So when + // deciding whether to resize, count the deleted buckets (which + // are currently taking up room). But later, when we decide what + // size to resize to, *don't* count deleted buckets, since they + // get discarded during the resize. + const size_type needed_size = settings.min_buckets(num_elements + delta, 0); + if ( needed_size <= bucket_count() ) // we have enough buckets + return did_resize; + + size_type resize_to = + settings.min_buckets(num_elements - num_deleted + delta, bucket_count()); + + if (resize_to < needed_size && // may double resize_to + resize_to < (STL_NAMESPACE::numeric_limits::max)() / 2) { + // This situation means that we have enough deleted elements, + // that once we purge them, we won't actually have needed to + // grow. But we may want to grow anyway: if we just purge one + // element, say, we'll have to grow anyway next time we + // insert. Might as well grow now, since we're already going + // through the trouble of copying (in order to purge the + // deleted elements). + const size_type target = + static_cast(settings.shrink_size(resize_to*2)); + if (num_elements - num_deleted + delta >= target) { + // Good, we won't be below the shrink threshhold even if we double. + resize_to *= 2; + } + } + dense_hashtable tmp(*this, resize_to); + swap(tmp); // now we are tmp + return true; + } + + // We require table be not-NULL and empty before calling this. + void resize_table(size_type /*old_size*/, size_type new_size, + true_type) { + table = val_info.realloc_or_die(table, new_size); + } + + void resize_table(size_type old_size, size_type new_size, false_type) { + val_info.deallocate(table, old_size); + table = val_info.allocate(new_size); + } + + // Used to actually do the rehashing when we grow/shrink a hashtable + void copy_from(const dense_hashtable &ht, size_type min_buckets_wanted) { + clear_to_size(settings.min_buckets(ht.size(), min_buckets_wanted)); + + // We use a normal iterator to get non-deleted bcks from ht + // We could use insert() here, but since we know there are + // no duplicates and no deleted items, we can be more efficient + assert((bucket_count() & (bucket_count()-1)) == 0); // a power of two + for ( const_iterator it = ht.begin(); it != ht.end(); ++it ) { + size_type num_probes = 0; // how many times we've probed + size_type bucknum; + const size_type bucket_count_minus_one = bucket_count() - 1; + for (bucknum = hash(get_key(*it)) & bucket_count_minus_one; + !test_empty(bucknum); // not empty + bucknum = (bucknum + JUMP_(key, num_probes)) & bucket_count_minus_one) { + ++num_probes; + assert(num_probes < bucket_count() + && "Hashtable is full: an error in key_equal<> or hash<>"); + } + set_value(&table[bucknum], *it); // copies the value to here + num_elements++; + } + settings.inc_num_ht_copies(); + } + + // Required by the spec for hashed associative container + public: + // Though the docs say this should be num_buckets, I think it's much + // more useful as num_elements. As a special feature, calling with + // req_elements==0 will cause us to shrink if we can, saving space. + void resize(size_type req_elements) { // resize to this or larger + if ( settings.consider_shrink() || req_elements == 0 ) + maybe_shrink(); + if ( req_elements > num_elements ) + resize_delta(req_elements - num_elements); + } + + // Get and change the value of shrink_factor and enlarge_factor. The + // description at the beginning of this file explains how to choose + // the values. Setting the shrink parameter to 0.0 ensures that the + // table never shrinks. + void get_resizing_parameters(float* shrink, float* grow) const { + *shrink = settings.shrink_factor(); + *grow = settings.enlarge_factor(); + } + void set_resizing_parameters(float shrink, float grow) { + settings.set_resizing_parameters(shrink, grow); + settings.reset_thresholds(bucket_count()); + } + + // CONSTRUCTORS -- as required by the specs, we take a size, + // but also let you specify a hashfunction, key comparator, + // and key extractor. We also define a copy constructor and =. + // DESTRUCTOR -- needs to free the table + explicit dense_hashtable(size_type expected_max_items_in_table = 0, + const HashFcn& hf = HashFcn(), + const EqualKey& eql = EqualKey(), + const ExtractKey& ext = ExtractKey(), + const SetKey& set = SetKey(), + const Alloc& alloc = Alloc()) + : settings(hf), + key_info(ext, set, eql), + num_deleted(0), + num_elements(0), + num_buckets(expected_max_items_in_table == 0 + ? HT_DEFAULT_STARTING_BUCKETS + : settings.min_buckets(expected_max_items_in_table, 0)), + val_info(alloc_impl(alloc)), + table(NULL) { + // table is NULL until emptyval is set. However, we set num_buckets + // here so we know how much space to allocate once emptyval is set + settings.reset_thresholds(bucket_count()); + } + + // As a convenience for resize(), we allow an optional second argument + // which lets you make this new hashtable a different size than ht + dense_hashtable(const dense_hashtable& ht, + size_type min_buckets_wanted = HT_DEFAULT_STARTING_BUCKETS) + : settings(ht.settings), + key_info(ht.key_info), + num_deleted(0), + num_elements(0), + num_buckets(0), + val_info(ht.val_info), + table(NULL) { + if (!ht.settings.use_empty()) { + // If use_empty isn't set, copy_from will crash, so we do our own copying. + assert(ht.empty()); + num_buckets = settings.min_buckets(ht.size(), min_buckets_wanted); + settings.reset_thresholds(bucket_count()); + return; + } + settings.reset_thresholds(bucket_count()); + copy_from(ht, min_buckets_wanted); // copy_from() ignores deleted entries + } + + dense_hashtable& operator= (const dense_hashtable& ht) { + if (&ht == this) return *this; // don't copy onto ourselves + if (!ht.settings.use_empty()) { + assert(ht.empty()); + dense_hashtable empty_table(ht); // empty table with ht's thresholds + this->swap(empty_table); + return *this; + } + settings = ht.settings; + key_info = ht.key_info; + set_value(&val_info.emptyval, ht.val_info.emptyval); + // copy_from() calls clear and sets num_deleted to 0 too + copy_from(ht, HT_MIN_BUCKETS); + // we purposefully don't copy the allocator, which may not be copyable + return *this; + } + + ~dense_hashtable() { + if (table) { + destroy_buckets(0, num_buckets); + val_info.deallocate(table, num_buckets); + } + } + + // Many STL algorithms use swap instead of copy constructors + void swap(dense_hashtable& ht) { + STL_NAMESPACE::swap(settings, ht.settings); + STL_NAMESPACE::swap(key_info, ht.key_info); + STL_NAMESPACE::swap(num_deleted, ht.num_deleted); + STL_NAMESPACE::swap(num_elements, ht.num_elements); + STL_NAMESPACE::swap(num_buckets, ht.num_buckets); + { value_type tmp; // for annoying reasons, swap() doesn't work + set_value(&tmp, val_info.emptyval); + set_value(&val_info.emptyval, ht.val_info.emptyval); + set_value(&ht.val_info.emptyval, tmp); + } + STL_NAMESPACE::swap(table, ht.table); + settings.reset_thresholds(bucket_count()); // this also resets consider_shrink + ht.settings.reset_thresholds(bucket_count()); + // we purposefully don't swap the allocator, which may not be swap-able + } + + private: + void clear_to_size(size_type new_num_buckets) { + if (!table) { + table = val_info.allocate(new_num_buckets); + } else { + destroy_buckets(0, num_buckets); + if (new_num_buckets != num_buckets) { // resize, if necessary + typedef integral_constant >::value> + realloc_ok; + resize_table(num_buckets, new_num_buckets, realloc_ok()); + } + } + assert(table); + fill_range_with_empty(table, table + new_num_buckets); + num_elements = 0; + num_deleted = 0; + num_buckets = new_num_buckets; // our new size + settings.reset_thresholds(bucket_count()); + } + + public: + // It's always nice to be able to clear a table without deallocating it + void clear() { + // If the table is already empty, and the number of buckets is + // already as we desire, there's nothing to do. + const size_type new_num_buckets = settings.min_buckets(0, 0); + if (num_elements == 0 && new_num_buckets == num_buckets) { + return; + } + clear_to_size(new_num_buckets); + } + + // Clear the table without resizing it. + // Mimicks the stl_hashtable's behaviour when clear()-ing in that it + // does not modify the bucket count + void clear_no_resize() { + if (num_elements > 0) { + assert(table); + destroy_buckets(0, num_buckets); + fill_range_with_empty(table, table + num_buckets); + } + // don't consider to shrink before another erase() + settings.reset_thresholds(bucket_count()); + num_elements = 0; + num_deleted = 0; + } + + // LOOKUP ROUTINES + private: + // Returns a pair of positions: 1st where the object is, 2nd where + // it would go if you wanted to insert it. 1st is ILLEGAL_BUCKET + // if object is not found; 2nd is ILLEGAL_BUCKET if it is. + // Note: because of deletions where-to-insert is not trivial: it's the + // first deleted bucket we see, as long as we don't find the key later + pair find_position(const key_type &key) const { + size_type num_probes = 0; // how many times we've probed + const size_type bucket_count_minus_one = bucket_count() - 1; + size_type bucknum = hash(key) & bucket_count_minus_one; + size_type insert_pos = ILLEGAL_BUCKET; // where we would insert + while ( 1 ) { // probe until something happens + if ( test_empty(bucknum) ) { // bucket is empty + if ( insert_pos == ILLEGAL_BUCKET ) // found no prior place to insert + return pair(ILLEGAL_BUCKET, bucknum); + else + return pair(ILLEGAL_BUCKET, insert_pos); + + } else if ( test_deleted(bucknum) ) {// keep searching, but mark to insert + if ( insert_pos == ILLEGAL_BUCKET ) + insert_pos = bucknum; + + } else if ( equals(key, get_key(table[bucknum])) ) { + return pair(bucknum, ILLEGAL_BUCKET); + } + ++num_probes; // we're doing another probe + bucknum = (bucknum + JUMP_(key, num_probes)) & bucket_count_minus_one; + assert(num_probes < bucket_count() + && "Hashtable is full: an error in key_equal<> or hash<>"); + } + } + + public: + iterator find(const key_type& key) { + if ( size() == 0 ) return end(); + pair pos = find_position(key); + if ( pos.first == ILLEGAL_BUCKET ) // alas, not there + return end(); + else + return iterator(this, table + pos.first, table + num_buckets, false); + } + + const_iterator find(const key_type& key) const { + if ( size() == 0 ) return end(); + pair pos = find_position(key); + if ( pos.first == ILLEGAL_BUCKET ) // alas, not there + return end(); + else + return const_iterator(this, table + pos.first, table+num_buckets, false); + } + + // This is a tr1 method: the bucket a given key is in, or what bucket + // it would be put in, if it were to be inserted. Shrug. + size_type bucket(const key_type& key) const { + pair pos = find_position(key); + return pos.first == ILLEGAL_BUCKET ? pos.second : pos.first; + } + + // Counts how many elements have key key. For maps, it's either 0 or 1. + size_type count(const key_type &key) const { + pair pos = find_position(key); + return pos.first == ILLEGAL_BUCKET ? 0 : 1; + } + + // Likewise, equal_range doesn't really make sense for us. Oh well. + pair equal_range(const key_type& key) { + iterator pos = find(key); // either an iterator or end + if (pos == end()) { + return pair(pos, pos); + } else { + const iterator startpos = pos++; + return pair(startpos, pos); + } + } + pair equal_range(const key_type& key) const { + const_iterator pos = find(key); // either an iterator or end + if (pos == end()) { + return pair(pos, pos); + } else { + const const_iterator startpos = pos++; + return pair(startpos, pos); + } + } + + + // INSERTION ROUTINES + private: + // Private method used by insert_noresize and find_or_insert. + iterator insert_at(const_reference obj, size_type pos) { + if (size() >= max_size()) + throw std::length_error("insert overflow"); + if ( test_deleted(pos) ) { // just replace if it's been del. + // shrug: shouldn't need to be const. + const_iterator delpos(this, table + pos, table + num_buckets, false); + clear_deleted(delpos); + assert( num_deleted > 0); + --num_deleted; // used to be, now it isn't + } else { + ++num_elements; // replacing an empty bucket + } + set_value(&table[pos], obj); + return iterator(this, table + pos, table + num_buckets, false); + } + + // If you know *this is big enough to hold obj, use this routine + pair insert_noresize(const_reference obj) { + // First, double-check we're not inserting delkey or emptyval + assert((!settings.use_empty() || !equals(get_key(obj), + get_key(val_info.emptyval))) + && "Inserting the empty key"); + assert((!settings.use_deleted() || !equals(get_key(obj), key_info.delkey)) + && "Inserting the deleted key"); + const pair pos = find_position(get_key(obj)); + if ( pos.first != ILLEGAL_BUCKET) { // object was already there + return pair(iterator(this, table + pos.first, + table + num_buckets, false), + false); // false: we didn't insert + } else { // pos.second says where to put it + return pair(insert_at(obj, pos.second), true); + } + } + + // Specializations of insert(it, it) depending on the power of the iterator: + // (1) Iterator supports operator-, resize before inserting + template + void insert(ForwardIterator f, ForwardIterator l, STL_NAMESPACE::forward_iterator_tag) { + size_t dist = STL_NAMESPACE::distance(f, l); + if (dist >= (std::numeric_limits::max)()) + throw std::length_error("insert-range overflow"); + resize_delta(static_cast(dist)); + for ( ; dist > 0; --dist, ++f) { + insert_noresize(*f); + } + } + + // (2) Arbitrary iterator, can't tell how much to resize + template + void insert(InputIterator f, InputIterator l, STL_NAMESPACE::input_iterator_tag) { + for ( ; f != l; ++f) + insert(*f); + } + + public: + // This is the normal insert routine, used by the outside world + pair insert(const_reference obj) { + resize_delta(1); // adding an object, grow if need be + return insert_noresize(obj); + } + + // When inserting a lot at a time, we specialize on the type of iterator + template + void insert(InputIterator f, InputIterator l) { + // specializes on iterator type + insert(f, l, typename STL_NAMESPACE::iterator_traits::iterator_category()); + } + + // DefaultValue is a functor that takes a key and returns a value_type + // representing the default value to be inserted if none is found. + template + value_type& find_or_insert(const key_type& key) { + // First, double-check we're not inserting emptykey or delkey + assert((!settings.use_empty() || !equals(key, get_key(val_info.emptyval))) + && "Inserting the empty key"); + assert((!settings.use_deleted() || !equals(key, key_info.delkey)) + && "Inserting the deleted key"); + const pair pos = find_position(key); + DefaultValue default_value; + if ( pos.first != ILLEGAL_BUCKET) { // object was already there + return table[pos.first]; + } else if (resize_delta(1)) { // needed to rehash to make room + // Since we resized, we can't use pos, so recalculate where to insert. + return *insert_noresize(default_value(key)).first; + } else { // no need to rehash, insert right here + return *insert_at(default_value(key), pos.second); + } + } + + // DELETION ROUTINES + size_type erase(const key_type& key) { + // First, double-check we're not trying to erase delkey or emptyval. + assert((!settings.use_empty() || !equals(key, get_key(val_info.emptyval))) + && "Erasing the empty key"); + assert((!settings.use_deleted() || !equals(key, key_info.delkey)) + && "Erasing the deleted key"); + const_iterator pos = find(key); // shrug: shouldn't need to be const + if ( pos != end() ) { + assert(!test_deleted(pos)); // or find() shouldn't have returned it + set_deleted(pos); + ++num_deleted; + settings.set_consider_shrink(true); // will think about shrink after next insert + return 1; // because we deleted one thing + } else { + return 0; // because we deleted nothing + } + } + + // We return the iterator past the deleted item. + void erase(iterator pos) { + if ( pos == end() ) return; // sanity check + if ( set_deleted(pos) ) { // true if object has been newly deleted + ++num_deleted; + settings.set_consider_shrink(true); // will think about shrink after next insert + } + } + + void erase(iterator f, iterator l) { + for ( ; f != l; ++f) { + if ( set_deleted(f) ) // should always be true + ++num_deleted; + } + settings.set_consider_shrink(true); // will think about shrink after next insert + } + + // We allow you to erase a const_iterator just like we allow you to + // erase an iterator. This is in parallel to 'delete': you can delete + // a const pointer just like a non-const pointer. The logic is that + // you can't use the object after it's erased anyway, so it doesn't matter + // if it's const or not. + void erase(const_iterator pos) { + if ( pos == end() ) return; // sanity check + if ( set_deleted(pos) ) { // true if object has been newly deleted + ++num_deleted; + settings.set_consider_shrink(true); // will think about shrink after next insert + } + } + void erase(const_iterator f, const_iterator l) { + for ( ; f != l; ++f) { + if ( set_deleted(f) ) // should always be true + ++num_deleted; + } + settings.set_consider_shrink(true); // will think about shrink after next insert + } + + + // COMPARISON + bool operator==(const dense_hashtable& ht) const { + if (size() != ht.size()) { + return false; + } else if (this == &ht) { + return true; + } else { + // Iterate through the elements in "this" and see if the + // corresponding element is in ht + for ( const_iterator it = begin(); it != end(); ++it ) { + const_iterator it2 = ht.find(get_key(*it)); + if ((it2 == ht.end()) || (*it != *it2)) { + return false; + } + } + return true; + } + } + bool operator!=(const dense_hashtable& ht) const { + return !(*this == ht); + } + + + // I/O + // We support reading and writing hashtables to disk. Alas, since + // I don't know how to write a hasher or key_equal, you have to make + // sure everything but the table is the same. We compact before writing + // + // NOTE: These functions are currently TODO. They've not been implemented. + bool write_metadata(FILE * /*fp*/) { + squash_deleted(); // so we don't have to worry about delkey + return false; // TODO + } + + bool read_metadata(FILE* /*fp*/) { + num_deleted = 0; // since we got rid before writing + assert(settings.use_empty() && "empty_key not set for read_metadata"); + if (table) val_info.deallocate(table, num_buckets); // we'll make our own + // TODO: read magic number + // TODO: read num_buckets + settings.reset_thresholds(bucket_count()); + table = val_info.allocate(num_buckets); + assert(table); + fill_range_with_empty(table, table + num_buckets); + // TODO: read num_elements + for ( size_type i = 0; i < num_elements; ++i ) { + // TODO: read bucket_num + // TODO: set with non-empty, non-deleted value + } + return false; // TODO + } + + // If your keys and values are simple enough, we can write them to + // disk for you. "simple enough" means value_type is a POD type + // that contains no pointers. However, we don't try to normalize + // endianness + bool write_nopointer_data(FILE *fp) const { + for ( const_iterator it = begin(); it != end(); ++it ) { + // TODO: skip empty/deleted values + if ( !fwrite(&*it, sizeof(*it), 1, fp) ) return false; + } + return false; + } + + // When reading, we have to override the potential const-ness of *it + bool read_nopointer_data(FILE *fp) { + for ( iterator it = begin(); it != end(); ++it ) { + // TODO: skip empty/deleted values + if ( !fread(reinterpret_cast(&(*it)), sizeof(*it), 1, fp) ) + return false; + } + return false; + } + + private: + template + class alloc_impl : public A { + public: + typedef typename A::pointer pointer; + typedef typename A::size_type size_type; + + // Convert a normal allocator to one that has realloc_or_die() + alloc_impl(const A& a) : A(a) { } + + // realloc_or_die should only be used when using the default + // allocator (libc_allocator_with_realloc). + pointer realloc_or_die(pointer /*ptr*/, size_type /*n*/) { + fprintf(stderr, "realloc_or_die is only supported for " + "libc_allocator_with_realloc"); + exit(1); + return NULL; + } + }; + + // A template specialization of alloc_impl for + // libc_allocator_with_realloc that can handle realloc_or_die. + template + class alloc_impl > + : public libc_allocator_with_realloc { + public: + typedef typename libc_allocator_with_realloc::pointer pointer; + typedef typename libc_allocator_with_realloc::size_type size_type; + + alloc_impl(const libc_allocator_with_realloc& a) + : libc_allocator_with_realloc(a) { } + + pointer realloc_or_die(pointer ptr, size_type n) { + pointer retval = this->reallocate(ptr, n); + if (retval == NULL) { + // We really should use PRIuS here, but I don't want to have to add + // a whole new configure option, with concomitant macro namespace + // pollution, just to print this (unlikely) error message. So I cast. + fprintf(stderr, "sparsehash: FATAL ERROR: failed to reallocate " + "%lu elements for ptr %p", + static_cast(n), ptr); + exit(1); + } + return retval; + } + }; + + // Package allocator with emptyval to eliminate memory needed for + // the zero-size allocator. + // If new fields are added to this class, we should add them to + // operator= and swap. + class ValInfo : public alloc_impl { + public: + typedef typename alloc_impl::value_type value_type; + + ValInfo(const alloc_impl& a) + : alloc_impl(a), emptyval() { } + ValInfo(const ValInfo& v) + : alloc_impl(v), emptyval(v.emptyval) { } + + value_type emptyval; // which key marks unused entries + }; + + + // Package functors with another class to eliminate memory needed for + // zero-size functors. Since ExtractKey and hasher's operator() might + // have the same function signature, they must be packaged in + // different classes. + struct Settings : + sh_hashtable_settings { + explicit Settings(const hasher& hf) + : sh_hashtable_settings( + hf, HT_OCCUPANCY_PCT / 100.0f, HT_EMPTY_PCT / 100.0f) {} + }; + + // Packages ExtractKey and SetKey functors. + class KeyInfo : public ExtractKey, public SetKey, public key_equal { + public: + KeyInfo(const ExtractKey& ek, const SetKey& sk, const key_equal& eq) + : ExtractKey(ek), + SetKey(sk), + key_equal(eq) { + } + + // We want to return the exact same type as ExtractKey: Key or const Key& + typename ExtractKey::result_type get_key(const_reference v) const { + return ExtractKey::operator()(v); + } + void set_key(pointer v, const key_type& k) const { + SetKey::operator()(v, k); + } + bool equals(const key_type& a, const key_type& b) const { + return key_equal::operator()(a, b); + } + + // Which key marks deleted entries. + // TODO(csilvers): make a pointer, and get rid of use_deleted (benchmark!) + typename remove_const::type delkey; + }; + + // Utility functions to access the templated operators + size_type hash(const key_type& v) const { + return settings.hash(v); + } + bool equals(const key_type& a, const key_type& b) const { + return key_info.equals(a, b); + } + typename ExtractKey::result_type get_key(const_reference v) const { + return key_info.get_key(v); + } + void set_key(pointer v, const key_type& k) const { + key_info.set_key(v, k); + } + + private: + // Actual data + Settings settings; + KeyInfo key_info; + + size_type num_deleted; // how many occupied buckets are marked deleted + size_type num_elements; + size_type num_buckets; + ValInfo val_info; // holds emptyval, and also the allocator + pointer table; +}; + + +// We need a global swap as well +template +inline void swap(dense_hashtable &x, + dense_hashtable &y) { + x.swap(y); +} + +#undef JUMP_ + +template +const typename dense_hashtable::size_type + dense_hashtable::ILLEGAL_BUCKET; + +// How full we let the table get before we resize. Knuth says .8 is +// good -- higher causes us to probe too much, though saves memory. +// However, we go with .5, getting better performance at the cost of +// more space (a trade-off densehashtable explicitly chooses to make). +// Feel free to play around with different values, though. +template +const int dense_hashtable::HT_OCCUPANCY_PCT = 50; + +// How empty we let the table get before we resize lower. +// It should be less than OCCUPANCY_PCT / 2 or we thrash resizing +template +const int dense_hashtable::HT_EMPTY_PCT + = static_cast(0.4 * + dense_hashtable::HT_OCCUPANCY_PCT); + +_END_GOOGLE_NAMESPACE_ + +#endif /* _DENSEHASHTABLE_H_ */ diff --git a/external/google/sparsehash/hashtable-common.h b/external/google/sparsehash/hashtable-common.h new file mode 100644 index 00000000..e823b124 --- /dev/null +++ b/external/google/sparsehash/hashtable-common.h @@ -0,0 +1,178 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// --- +// Author: Giao Nguyen + +#ifndef UTIL_GTL_HASHTABLE_COMMON_H_ +#define UTIL_GTL_HASHTABLE_COMMON_H_ + +#include + +// Settings contains parameters for growing and shrinking the table. +// It also packages zero-size functor (ie. hasher). + +template +class sh_hashtable_settings : public HashFunc { + public: + typedef Key key_type; + typedef HashFunc hasher; + typedef SizeType size_type; + + public: + sh_hashtable_settings(const hasher& hf, + const float ht_occupancy_flt, + const float ht_empty_flt) + : hasher(hf), + enlarge_threshold_(0), + shrink_threshold_(0), + consider_shrink_(false), + use_empty_(false), + use_deleted_(false), + num_ht_copies_(0) { + set_enlarge_factor(ht_occupancy_flt); + set_shrink_factor(ht_empty_flt); + } + + size_type hash(const key_type& v) const { + return hasher::operator()(v); + } + + float enlarge_factor() const { + return enlarge_factor_; + } + void set_enlarge_factor(float f) { + enlarge_factor_ = f; + } + float shrink_factor() const { + return shrink_factor_; + } + void set_shrink_factor(float f) { + shrink_factor_ = f; + } + + size_type enlarge_threshold() const { + return enlarge_threshold_; + } + void set_enlarge_threshold(size_type t) { + enlarge_threshold_ = t; + } + size_type shrink_threshold() const { + return shrink_threshold_; + } + void set_shrink_threshold(size_type t) { + shrink_threshold_ = t; + } + + size_type enlarge_size(size_type x) const { + return static_cast(x * enlarge_factor_); + } + size_type shrink_size(size_type x) const { + return static_cast(x * shrink_factor_); + } + + bool consider_shrink() const { + return consider_shrink_; + } + void set_consider_shrink(bool t) { + consider_shrink_ = t; + } + + bool use_empty() const { + return use_empty_; + } + void set_use_empty(bool t) { + use_empty_ = t; + } + + bool use_deleted() const { + return use_deleted_; + } + void set_use_deleted(bool t) { + use_deleted_ = t; + } + + size_type num_ht_copies() const { + return static_cast(num_ht_copies_); + } + void inc_num_ht_copies() { + ++num_ht_copies_; + } + + // Reset the enlarge and shrink thresholds + void reset_thresholds(size_type num_buckets) { + set_enlarge_threshold(enlarge_size(num_buckets)); + set_shrink_threshold(shrink_size(num_buckets)); + // whatever caused us to reset already considered + set_consider_shrink(false); + } + + // Caller is resposible for calling reset_threshold right after + // set_resizing_parameters. + void set_resizing_parameters(float shrink, float grow) { + assert(shrink >= 0.0); + assert(grow <= 1.0); + if (shrink > grow/2.0f) + shrink = grow / 2.0f; // otherwise we thrash hashtable size + set_shrink_factor(shrink); + set_enlarge_factor(grow); + } + + // This is the smallest size a hashtable can be without being too crowded + // If you like, you can give a min #buckets as well as a min #elts + size_type min_buckets(size_type num_elts, size_type min_buckets_wanted) { + float enlarge = enlarge_factor(); + size_type sz = HT_MIN_BUCKETS; // min buckets allowed + while ( sz < min_buckets_wanted || + num_elts >= static_cast(sz * enlarge) ) { + // This just prevents overflowing size_type, since sz can exceed + // max_size() here. + if (static_cast(sz * 2) < sz) { + throw std::length_error("resize overflow"); // protect against overflow + } + sz *= 2; + } + return sz; + } + + private: + size_type enlarge_threshold_; // table.size() * enlarge_factor + size_type shrink_threshold_; // table.size() * shrink_factor + float enlarge_factor_; // how full before resize + float shrink_factor_; // how empty before resize + // consider_shrink=true if we should try to shrink before next insert + bool consider_shrink_; + bool use_empty_; // used only by densehashtable, not sparsehashtable + bool use_deleted_; // false until delkey has been set + // num_ht_copies is a counter incremented every Copy/Move + unsigned int num_ht_copies_; +}; + +#endif // UTIL_GTL_HASHTABLE_COMMON_H_ diff --git a/external/google/sparsehash/libc_allocator_with_realloc.h b/external/google/sparsehash/libc_allocator_with_realloc.h new file mode 100644 index 00000000..4ba1db48 --- /dev/null +++ b/external/google/sparsehash/libc_allocator_with_realloc.h @@ -0,0 +1,121 @@ +// Copyright (c) 2010, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// --- +// Author: Guilin Chen + +#ifndef UTIL_GTL_LIBC_ALLOCATOR_WITH_REALLOC_H_ +#define UTIL_GTL_LIBC_ALLOCATOR_WITH_REALLOC_H_ + +#include + +#include // for malloc/realloc/free +#include // for ptrdiff_t + + +_START_GOOGLE_NAMESPACE_ + +template +class libc_allocator_with_realloc { + public: + typedef T value_type; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + + libc_allocator_with_realloc() {} + libc_allocator_with_realloc(const libc_allocator_with_realloc&) {} + ~libc_allocator_with_realloc() {} + + pointer address(reference r) const { return &r; } + const_pointer address(const_reference r) const { return &r; } + + pointer allocate(size_type n, const_pointer = 0) { + return static_cast(malloc(n * sizeof(value_type))); + } + void deallocate(pointer p, size_type) { + free(p); + } + pointer reallocate(pointer p, size_type n) { + return static_cast(realloc(p, n * sizeof(value_type))); + } + + size_type max_size() const { + return static_cast(-1) / sizeof(value_type); + } + + void construct(pointer p, const value_type& val) { + new(p) value_type(val); + } + void destroy(pointer p) { p->~value_type(); } + + template + libc_allocator_with_realloc(const libc_allocator_with_realloc&) {} + + template + struct rebind { + typedef libc_allocator_with_realloc other; + }; +}; + +// libc_allocator_with_realloc specialization. +template<> +class libc_allocator_with_realloc { + public: + typedef void value_type; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + typedef void* pointer; + typedef const void* const_pointer; + + template + struct rebind { + typedef libc_allocator_with_realloc other; + }; +}; + +template +inline bool operator==(const libc_allocator_with_realloc&, + const libc_allocator_with_realloc&) { + return true; +} + +template +inline bool operator!=(const libc_allocator_with_realloc&, + const libc_allocator_with_realloc&) { + return false; +} + +_END_GOOGLE_NAMESPACE_ + +#endif // UTIL_GTL_LIBC_ALLOCATOR_WITH_REALLOC_H_ diff --git a/external/google/sparsehash/os_config.h b/external/google/sparsehash/os_config.h new file mode 100644 index 00000000..0a397192 --- /dev/null +++ b/external/google/sparsehash/os_config.h @@ -0,0 +1,37 @@ +#ifndef _MSC_VER +//non-win version + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if the system has the type `uint16_t'. */ +#define HAVE_UINT16_T 1 + +/* Define to 1 if the system has the type `u_int16_t'. */ +#define HAVE_U_INT16_T 1 + +/* Define to 1 if the system has the type `__uint16'. */ +/* #undef HAVE___UINT16 */ + +#else +//win version + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if the system has the type `uint16_t'. */ +#undef HAVE_UINT16_T + +/* Define to 1 if the system has the type `u_int16_t'. */ +#undef HAVE_U_INT16_T + +/* Define to 1 if the system has the type `__uint16'. */ +#define HAVE___UINT16 1 + +#endif diff --git a/external/google/sparsehash/sparseconfig.h b/external/google/sparsehash/sparseconfig.h new file mode 100644 index 00000000..fbfa328a --- /dev/null +++ b/external/google/sparsehash/sparseconfig.h @@ -0,0 +1,39 @@ +/* + * NOTE: This file is for internal use only. + * Do not use these #defines in your own program! + */ + +/* Namespace for Google classes */ +#define GOOGLE_NAMESPACE ::google + +/* the location of the header defining hash functions */ +#define HASH_FUN_H + +/* the namespace of the hash<> function */ +#define HASH_NAMESPACE std + +/* Define to 1 if the system has the type `long long'. */ +#define HAVE_LONG_LONG 1 + +/* Define to 1 if you have the `memcpy' function. */ +#define HAVE_MEMCPY 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* The system-provided hash function including the namespace. */ +#define SPARSEHASH_HASH HASH_NAMESPACE::hash + +/* The system-provided hash function, in namespace HASH_NAMESPACE. */ +#define SPARSEHASH_HASH_NO_NAMESPACE hash + +/* the namespace where STL code like vector<> is defined */ +#define STL_NAMESPACE std + +/* Stops putting the code inside the Google namespace */ +#define _END_GOOGLE_NAMESPACE_ } + +/* Puts following code inside the Google namespace */ +#define _START_GOOGLE_NAMESPACE_ namespace google { + +#include "os_config.h" diff --git a/external/google/sparsehash/sparseconfig_win.h b/external/google/sparsehash/sparseconfig_win.h new file mode 100644 index 00000000..a4607139 --- /dev/null +++ b/external/google/sparsehash/sparseconfig_win.h @@ -0,0 +1,37 @@ +/* + * NOTE: This file is for internal use only. + * Do not use these #defines in your own program! + */ + +/* Namespace for Google classes */ +#define GOOGLE_NAMESPACE ::google + +/* the location of the header defining hash functions */ +#define HASH_FUN_H + +/* the namespace of the hash<> function */ +#define HASH_NAMESPACE std + +/* Define to 1 if the system has the type `long long'. */ +#define HAVE_LONG_LONG 1 + +/* Define to 1 if you have the `memcpy' function. */ +#define HAVE_MEMCPY 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* The system-provided hash function including the namespace. */ +#define SPARSEHASH_HASH HASH_NAMESPACE::hash + +/* The system-provided hash function, in namespace HASH_NAMESPACE. */ +#define SPARSEHASH_HASH_NO_NAMESPACE hash + +/* the namespace where STL code like vector<> is defined */ +#define STL_NAMESPACE std + +/* Stops putting the code inside the Google namespace */ +#define _END_GOOGLE_NAMESPACE_ } + +/* Puts following code inside the Google namespace */ +#define _START_GOOGLE_NAMESPACE_ namespace google { diff --git a/external/google/sparsehash/sparsehashtable.h b/external/google/sparsehash/sparsehashtable.h new file mode 100644 index 00000000..9f2d1443 --- /dev/null +++ b/external/google/sparsehash/sparsehashtable.h @@ -0,0 +1,1190 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// --- +// Author: Craig Silverstein +// +// A sparse hashtable is a particular implementation of +// a hashtable: one that is meant to minimize memory use. +// It does this by using a *sparse table* (cf sparsetable.h), +// which uses between 1 and 2 bits to store empty buckets +// (we may need another bit for hashtables that support deletion). +// +// When empty buckets are so cheap, an appealing hashtable +// implementation is internal probing, in which the hashtable +// is a single table, and collisions are resolved by trying +// to insert again in another bucket. The most cache-efficient +// internal probing schemes are linear probing (which suffers, +// alas, from clumping) and quadratic probing, which is what +// we implement by default. +// +// Deleted buckets are a bit of a pain. We have to somehow mark +// deleted buckets (the probing must distinguish them from empty +// buckets). The most principled way is to have another bitmap, +// but that's annoying and takes up space. Instead we let the +// user specify an "impossible" key. We set deleted buckets +// to have the impossible key. +// +// Note it is possible to change the value of the delete key +// on the fly; you can even remove it, though after that point +// the hashtable is insert_only until you set it again. +// +// You probably shouldn't use this code directly. Use +// or instead. +// +// You can modify the following, below: +// HT_OCCUPANCY_PCT -- how full before we double size +// HT_EMPTY_PCT -- how empty before we halve size +// HT_MIN_BUCKETS -- smallest bucket size +// HT_DEFAULT_STARTING_BUCKETS -- default bucket size at construct-time +// +// You can also change enlarge_factor (which defaults to +// HT_OCCUPANCY_PCT), and shrink_factor (which defaults to +// HT_EMPTY_PCT) with set_resizing_parameters(). +// +// How to decide what values to use? +// shrink_factor's default of .4 * OCCUPANCY_PCT, is probably good. +// HT_MIN_BUCKETS is probably unnecessary since you can specify +// (indirectly) the starting number of buckets at construct-time. +// For enlarge_factor, you can use this chart to try to trade-off +// expected lookup time to the space taken up. By default, this +// code uses quadratic probing, though you can change it to linear +// via _JUMP below if you really want to. +// +// From http://www.augustana.ca/~mohrj/courses/1999.fall/csc210/lecture_notes/hashing.html +// NUMBER OF PROBES / LOOKUP Successful Unsuccessful +// Quadratic collision resolution 1 - ln(1-L) - L/2 1/(1-L) - L - ln(1-L) +// Linear collision resolution [1+1/(1-L)]/2 [1+1/(1-L)2]/2 +// +// -- enlarge_factor -- 0.10 0.50 0.60 0.75 0.80 0.90 0.99 +// QUADRATIC COLLISION RES. +// probes/successful lookup 1.05 1.44 1.62 2.01 2.21 2.85 5.11 +// probes/unsuccessful lookup 1.11 2.19 2.82 4.64 5.81 11.4 103.6 +// LINEAR COLLISION RES. +// probes/successful lookup 1.06 1.5 1.75 2.5 3.0 5.5 50.5 +// probes/unsuccessful lookup 1.12 2.5 3.6 8.5 13.0 50.0 5000.0 +// +// The value type is required to be copy constructible and default +// constructible, but it need not be (and commonly isn't) assignable. + +#ifndef _SPARSEHASHTABLE_H_ +#define _SPARSEHASHTABLE_H_ + +#ifndef SPARSEHASH_STAT_UPDATE +#define SPARSEHASH_STAT_UPDATE(x) ((void) 0) +#endif + +// The probing method +// Linear probing +// #define JUMP_(key, num_probes) ( 1 ) +// Quadratic probing +#define JUMP_(key, num_probes) ( num_probes ) + +#include +#include +#include // For swap(), eg +#include // For length_error +#include // for facts about iterator tags +#include // for numeric_limits<> +#include // for pair<> +#include +#include // Since that's basically what we are + +_START_GOOGLE_NAMESPACE_ + +using STL_NAMESPACE::pair; + +// The smaller this is, the faster lookup is (because the group bitmap is +// smaller) and the faster insert is, because there's less to move. +// On the other hand, there are more groups. Since group::size_type is +// a short, this number should be of the form 32*x + 16 to avoid waste. +static const u_int16_t DEFAULT_GROUP_SIZE = 48; // fits in 1.5 words + +// Hashtable class, used to implement the hashed associative containers +// hash_set and hash_map. +// +// Value: what is stored in the table (each bucket is a Value). +// Key: something in a 1-to-1 correspondence to a Value, that can be used +// to search for a Value in the table (find() takes a Key). +// HashFcn: Takes a Key and returns an integer, the more unique the better. +// ExtractKey: given a Value, returns the unique Key associated with it. +// Must inherit from unary_function, or at least have a +// result_type enum indicating the return type of operator(). +// SetKey: given a Value* and a Key, modifies the value such that +// ExtractKey(value) == key. We guarantee this is only called +// with key == deleted_key. +// EqualKey: Given two Keys, says whether they are the same (that is, +// if they are both associated with the same Value). +// Alloc: STL allocator to use to allocate memory. + +template +class sparse_hashtable; + +template +struct sparse_hashtable_iterator; + +template +struct sparse_hashtable_const_iterator; + +// As far as iterating, we're basically just a sparsetable +// that skips over deleted elements. +template +struct sparse_hashtable_iterator { + private: + typedef typename A::template rebind::other value_alloc_type; + + public: + typedef sparse_hashtable_iterator iterator; + typedef sparse_hashtable_const_iterator const_iterator; + typedef typename sparsetable::nonempty_iterator + st_iterator; + + typedef STL_NAMESPACE::forward_iterator_tag iterator_category; + typedef V value_type; + typedef typename value_alloc_type::difference_type difference_type; + typedef typename value_alloc_type::size_type size_type; + typedef typename value_alloc_type::reference reference; + typedef typename value_alloc_type::pointer pointer; + + // "Real" constructor and default constructor + sparse_hashtable_iterator(const sparse_hashtable *h, + st_iterator it, st_iterator it_end) + : ht(h), pos(it), end(it_end) { advance_past_deleted(); } + sparse_hashtable_iterator() { } // not ever used internally + // The default destructor is fine; we don't define one + // The default operator= is fine; we don't define one + + // Happy dereferencer + reference operator*() const { return *pos; } + pointer operator->() const { return &(operator*()); } + + // Arithmetic. The only hard part is making sure that + // we're not on a marked-deleted array element + void advance_past_deleted() { + while ( pos != end && ht->test_deleted(*this) ) + ++pos; + } + iterator& operator++() { + assert(pos != end); ++pos; advance_past_deleted(); return *this; + } + iterator operator++(int) { iterator tmp(*this); ++*this; return tmp; } + + // Comparison. + bool operator==(const iterator& it) const { return pos == it.pos; } + bool operator!=(const iterator& it) const { return pos != it.pos; } + + + // The actual data + const sparse_hashtable *ht; + st_iterator pos, end; +}; + +// Now do it all again, but with const-ness! +template +struct sparse_hashtable_const_iterator { + private: + typedef typename A::template rebind::other value_alloc_type; + + public: + typedef sparse_hashtable_iterator iterator; + typedef sparse_hashtable_const_iterator const_iterator; + typedef typename sparsetable::const_nonempty_iterator + st_iterator; + + typedef STL_NAMESPACE::forward_iterator_tag iterator_category; + typedef V value_type; + typedef typename value_alloc_type::difference_type difference_type; + typedef typename value_alloc_type::size_type size_type; + typedef typename value_alloc_type::const_reference reference; + typedef typename value_alloc_type::const_pointer pointer; + + // "Real" constructor and default constructor + sparse_hashtable_const_iterator(const sparse_hashtable *h, + st_iterator it, st_iterator it_end) + : ht(h), pos(it), end(it_end) { advance_past_deleted(); } + // This lets us convert regular iterators to const iterators + sparse_hashtable_const_iterator() { } // never used internally + sparse_hashtable_const_iterator(const iterator &it) + : ht(it.ht), pos(it.pos), end(it.end) { } + // The default destructor is fine; we don't define one + // The default operator= is fine; we don't define one + + // Happy dereferencer + reference operator*() const { return *pos; } + pointer operator->() const { return &(operator*()); } + + // Arithmetic. The only hard part is making sure that + // we're not on a marked-deleted array element + void advance_past_deleted() { + while ( pos != end && ht->test_deleted(*this) ) + ++pos; + } + const_iterator& operator++() { + assert(pos != end); ++pos; advance_past_deleted(); return *this; + } + const_iterator operator++(int) { const_iterator tmp(*this); ++*this; return tmp; } + + // Comparison. + bool operator==(const const_iterator& it) const { return pos == it.pos; } + bool operator!=(const const_iterator& it) const { return pos != it.pos; } + + + // The actual data + const sparse_hashtable *ht; + st_iterator pos, end; +}; + +// And once again, but this time freeing up memory as we iterate +template +struct sparse_hashtable_destructive_iterator { + private: + typedef typename A::template rebind::other value_alloc_type; + + public: + typedef sparse_hashtable_destructive_iterator iterator; + typedef typename sparsetable::destructive_iterator + st_iterator; + + typedef STL_NAMESPACE::forward_iterator_tag iterator_category; + typedef V value_type; + typedef typename value_alloc_type::difference_type difference_type; + typedef typename value_alloc_type::size_type size_type; + typedef typename value_alloc_type::reference reference; + typedef typename value_alloc_type::pointer pointer; + + // "Real" constructor and default constructor + sparse_hashtable_destructive_iterator(const + sparse_hashtable *h, + st_iterator it, st_iterator it_end) + : ht(h), pos(it), end(it_end) { advance_past_deleted(); } + sparse_hashtable_destructive_iterator() { } // never used internally + // The default destructor is fine; we don't define one + // The default operator= is fine; we don't define one + + // Happy dereferencer + reference operator*() const { return *pos; } + pointer operator->() const { return &(operator*()); } + + // Arithmetic. The only hard part is making sure that + // we're not on a marked-deleted array element + void advance_past_deleted() { + while ( pos != end && ht->test_deleted(*this) ) + ++pos; + } + iterator& operator++() { + assert(pos != end); ++pos; advance_past_deleted(); return *this; + } + iterator operator++(int) { iterator tmp(*this); ++*this; return tmp; } + + // Comparison. + bool operator==(const iterator& it) const { return pos == it.pos; } + bool operator!=(const iterator& it) const { return pos != it.pos; } + + + // The actual data + const sparse_hashtable *ht; + st_iterator pos, end; +}; + + +template +class sparse_hashtable { + private: + typedef typename Alloc::template rebind::other value_alloc_type; + + public: + typedef Key key_type; + typedef Value value_type; + typedef HashFcn hasher; + typedef EqualKey key_equal; + typedef Alloc allocator_type; + + typedef typename value_alloc_type::size_type size_type; + typedef typename value_alloc_type::difference_type difference_type; + typedef typename value_alloc_type::reference reference; + typedef typename value_alloc_type::const_reference const_reference; + typedef typename value_alloc_type::pointer pointer; + typedef typename value_alloc_type::const_pointer const_pointer; + typedef sparse_hashtable_iterator + iterator; + + typedef sparse_hashtable_const_iterator + const_iterator; + + typedef sparse_hashtable_destructive_iterator + destructive_iterator; + + // These come from tr1. For us they're the same as regular iterators. + typedef iterator local_iterator; + typedef const_iterator const_local_iterator; + + // How full we let the table get before we resize, by default. + // Knuth says .8 is good -- higher causes us to probe too much, + // though it saves memory. + static const int HT_OCCUPANCY_PCT; // = 80 (out of 100); + + // How empty we let the table get before we resize lower, by default. + // (0.0 means never resize lower.) + // It should be less than OCCUPANCY_PCT / 2 or we thrash resizing + static const int HT_EMPTY_PCT; // = 0.4 * HT_OCCUPANCY_PCT; + + // Minimum size we're willing to let hashtables be. + // Must be a power of two, and at least 4. + // Note, however, that for a given hashtable, the initial size is a + // function of the first constructor arg, and may be >HT_MIN_BUCKETS. + static const size_type HT_MIN_BUCKETS = 4; + + // By default, if you don't specify a hashtable size at + // construction-time, we use this size. Must be a power of two, and + // at least HT_MIN_BUCKETS. + static const size_type HT_DEFAULT_STARTING_BUCKETS = 32; + + // ITERATOR FUNCTIONS + iterator begin() { return iterator(this, table.nonempty_begin(), + table.nonempty_end()); } + iterator end() { return iterator(this, table.nonempty_end(), + table.nonempty_end()); } + const_iterator begin() const { return const_iterator(this, + table.nonempty_begin(), + table.nonempty_end()); } + const_iterator end() const { return const_iterator(this, + table.nonempty_end(), + table.nonempty_end()); } + + // These come from tr1 unordered_map. They iterate over 'bucket' n. + // For sparsehashtable, we could consider each 'group' to be a bucket, + // I guess, but I don't really see the point. We'll just consider + // bucket n to be the n-th element of the sparsetable, if it's occupied, + // or some empty element, otherwise. + local_iterator begin(size_type i) { + if (table.test(i)) + return local_iterator(this, table.get_iter(i), table.nonempty_end()); + else + return local_iterator(this, table.nonempty_end(), table.nonempty_end()); + } + local_iterator end(size_type i) { + local_iterator it = begin(i); + if (table.test(i) && !test_deleted(i)) + ++it; + return it; + } + const_local_iterator begin(size_type i) const { + if (table.test(i)) + return const_local_iterator(this, table.get_iter(i), + table.nonempty_end()); + else + return const_local_iterator(this, table.nonempty_end(), + table.nonempty_end()); + } + const_local_iterator end(size_type i) const { + const_local_iterator it = begin(i); + if (table.test(i) && !test_deleted(i)) + ++it; + return it; + } + + // This is used when resizing + destructive_iterator destructive_begin() { + return destructive_iterator(this, table.destructive_begin(), + table.destructive_end()); + } + destructive_iterator destructive_end() { + return destructive_iterator(this, table.destructive_end(), + table.destructive_end()); + } + + + // ACCESSOR FUNCTIONS for the things we templatize on, basically + hasher hash_funct() const { return settings; } + key_equal key_eq() const { return key_info; } + allocator_type get_allocator() const { return table.get_allocator(); } + + // Accessor function for statistics gathering. + int num_table_copies() const { return settings.num_ht_copies(); } + + private: + // We need to copy values when we set the special marker for deleted + // elements, but, annoyingly, we can't just use the copy assignment + // operator because value_type might not be assignable (it's often + // pair). We use explicit destructor invocation and + // placement new to get around this. Arg. + void set_value(pointer dst, const_reference src) { + dst->~value_type(); // delete the old value, if any + new(dst) value_type(src); + } + + // This is used as a tag for the copy constructor, saying to destroy its + // arg We have two ways of destructively copying: with potentially growing + // the hashtable as we copy, and without. To make sure the outside world + // can't do a destructive copy, we make the typename private. + enum MoveDontCopyT {MoveDontCopy, MoveDontGrow}; + + // DELETE HELPER FUNCTIONS + // This lets the user describe a key that will indicate deleted + // table entries. This key should be an "impossible" entry -- + // if you try to insert it for real, you won't be able to retrieve it! + // (NB: while you pass in an entire value, only the key part is looked + // at. This is just because I don't know how to assign just a key.) + private: + void squash_deleted() { // gets rid of any deleted entries we have + if ( num_deleted ) { // get rid of deleted before writing + sparse_hashtable tmp(MoveDontGrow, *this); + swap(tmp); // now we are tmp + } + assert(num_deleted == 0); + } + + bool test_deleted_key(const key_type& key) const { + // The num_deleted test is crucial for read(): after read(), the ht values + // are garbage, and we don't want to think some of them are deleted. + // Invariant: !use_deleted implies num_deleted is 0. + assert(settings.use_deleted() || num_deleted == 0); + return num_deleted > 0 && equals(key_info.delkey, key); + } + + public: + void set_deleted_key(const key_type &key) { + // It's only safe to change what "deleted" means if we purge deleted guys + squash_deleted(); + settings.set_use_deleted(true); + key_info.delkey = key; + } + void clear_deleted_key() { + squash_deleted(); + settings.set_use_deleted(false); + } + key_type deleted_key() const { + assert(settings.use_deleted() + && "Must set deleted key before calling deleted_key"); + return key_info.delkey; + } + + // These are public so the iterators can use them + // True if the item at position bucknum is "deleted" marker + bool test_deleted(size_type bucknum) const { + if (num_deleted == 0 || !table.test(bucknum)) return false; + return test_deleted_key(get_key(table.unsafe_get(bucknum))); + } + bool test_deleted(const iterator &it) const { + if (!settings.use_deleted()) return false; + return test_deleted_key(get_key(*it)); + } + bool test_deleted(const const_iterator &it) const { + if (!settings.use_deleted()) return false; + return test_deleted_key(get_key(*it)); + } + bool test_deleted(const destructive_iterator &it) const { + if (!settings.use_deleted()) return false; + return test_deleted_key(get_key(*it)); + } + + private: + // Set it so test_deleted is true. true if object didn't used to be deleted. + // TODO(csilvers): make these private (also in densehashtable.h) + bool set_deleted(iterator &it) { + assert(settings.use_deleted()); + bool retval = !test_deleted(it); + // &* converts from iterator to value-type. + set_key(&(*it), key_info.delkey); + return retval; + } + // Set it so test_deleted is false. true if object used to be deleted. + bool clear_deleted(iterator &it) { + assert(settings.use_deleted()); + // Happens automatically when we assign something else in its place. + return test_deleted(it); + } + + // We also allow to set/clear the deleted bit on a const iterator. + // We allow a const_iterator for the same reason you can delete a + // const pointer: it's convenient, and semantically you can't use + // 'it' after it's been deleted anyway, so its const-ness doesn't + // really matter. + bool set_deleted(const_iterator &it) { + assert(settings.use_deleted()); // bad if set_deleted_key() wasn't called + bool retval = !test_deleted(it); + set_key(const_cast(&(*it)), key_info.delkey); + return retval; + } + // Set it so test_deleted is false. true if object used to be deleted. + bool clear_deleted(const_iterator &it) { + assert(settings.use_deleted()); // bad if set_deleted_key() wasn't called + return test_deleted(it); + } + + // FUNCTIONS CONCERNING SIZE + public: + size_type size() const { return table.num_nonempty() - num_deleted; } + size_type max_size() const { return table.max_size(); } + bool empty() const { return size() == 0; } + size_type bucket_count() const { return table.size(); } + size_type max_bucket_count() const { return max_size(); } + // These are tr1 methods. Their idea of 'bucket' doesn't map well to + // what we do. We just say every bucket has 0 or 1 items in it. + size_type bucket_size(size_type i) const { + return begin(i) == end(i) ? 0 : 1; + } + + private: + // Because of the above, size_type(-1) is never legal; use it for errors + static const size_type ILLEGAL_BUCKET = size_type(-1); + + // Used after a string of deletes. Returns true if we actually shrunk. + // TODO(csilvers): take a delta so we can take into account inserts + // done after shrinking. Maybe make part of the Settings class? + bool maybe_shrink() { + assert(table.num_nonempty() >= num_deleted); + assert((bucket_count() & (bucket_count()-1)) == 0); // is a power of two + assert(bucket_count() >= HT_MIN_BUCKETS); + bool retval = false; + + // If you construct a hashtable with < HT_DEFAULT_STARTING_BUCKETS, + // we'll never shrink until you get relatively big, and we'll never + // shrink below HT_DEFAULT_STARTING_BUCKETS. Otherwise, something + // like "dense_hash_set x; x.insert(4); x.erase(4);" will + // shrink us down to HT_MIN_BUCKETS buckets, which is too small. + const size_type num_remain = table.num_nonempty() - num_deleted; + const size_type shrink_threshold = settings.shrink_threshold(); + if (shrink_threshold > 0 && num_remain < shrink_threshold && + bucket_count() > HT_DEFAULT_STARTING_BUCKETS) { + const float shrink_factor = settings.shrink_factor(); + size_type sz = bucket_count() / 2; // find how much we should shrink + while (sz > HT_DEFAULT_STARTING_BUCKETS && + num_remain < static_cast(sz * shrink_factor)) { + sz /= 2; // stay a power of 2 + } + sparse_hashtable tmp(MoveDontCopy, *this, sz); + swap(tmp); // now we are tmp + retval = true; + } + settings.set_consider_shrink(false); // because we just considered it + return retval; + } + + // We'll let you resize a hashtable -- though this makes us copy all! + // When you resize, you say, "make it big enough for this many more elements" + // Returns true if we actually resized, false if size was already ok. + bool resize_delta(size_type delta) { + bool did_resize = false; + if ( settings.consider_shrink() ) { // see if lots of deletes happened + if ( maybe_shrink() ) + did_resize = true; + } + if (table.num_nonempty() >= + (STL_NAMESPACE::numeric_limits::max)() - delta) + throw std::length_error("resize overflow"); + if ( bucket_count() >= HT_MIN_BUCKETS && + (table.num_nonempty() + delta) <= settings.enlarge_threshold() ) + return did_resize; // we're ok as we are + + // Sometimes, we need to resize just to get rid of all the + // "deleted" buckets that are clogging up the hashtable. So when + // deciding whether to resize, count the deleted buckets (which + // are currently taking up room). But later, when we decide what + // size to resize to, *don't* count deleted buckets, since they + // get discarded during the resize. + const size_type needed_size = + settings.min_buckets(table.num_nonempty() + delta, 0); + if ( needed_size <= bucket_count() ) // we have enough buckets + return did_resize; + + size_type resize_to = + settings.min_buckets(table.num_nonempty() - num_deleted + delta, + bucket_count()); + if (resize_to < needed_size && // may double resize_to + resize_to < (STL_NAMESPACE::numeric_limits::max)() / 2) { + // This situation means that we have enough deleted elements, + // that once we purge them, we won't actually have needed to + // grow. But we may want to grow anyway: if we just purge one + // element, say, we'll have to grow anyway next time we + // insert. Might as well grow now, since we're already going + // through the trouble of copying (in order to purge the + // deleted elements). + const size_type target = + static_cast(settings.shrink_size(resize_to*2)); + if (table.num_nonempty() - num_deleted + delta >= target) { + // Good, we won't be below the shrink threshhold even if we double. + resize_to *= 2; + } + } + + sparse_hashtable tmp(MoveDontCopy, *this, resize_to); + swap(tmp); // now we are tmp + return true; + } + + // Used to actually do the rehashing when we grow/shrink a hashtable + void copy_from(const sparse_hashtable &ht, size_type min_buckets_wanted) { + clear(); // clear table, set num_deleted to 0 + + // If we need to change the size of our table, do it now + const size_type resize_to = + settings.min_buckets(ht.size(), min_buckets_wanted); + if ( resize_to > bucket_count() ) { // we don't have enough buckets + table.resize(resize_to); // sets the number of buckets + settings.reset_thresholds(bucket_count()); + } + + // We use a normal iterator to get non-deleted bcks from ht + // We could use insert() here, but since we know there are + // no duplicates and no deleted items, we can be more efficient + assert((bucket_count() & (bucket_count()-1)) == 0); // a power of two + for ( const_iterator it = ht.begin(); it != ht.end(); ++it ) { + size_type num_probes = 0; // how many times we've probed + size_type bucknum; + const size_type bucket_count_minus_one = bucket_count() - 1; + for (bucknum = hash(get_key(*it)) & bucket_count_minus_one; + table.test(bucknum); // not empty + bucknum = (bucknum + JUMP_(key, num_probes)) & bucket_count_minus_one) { + ++num_probes; + assert(num_probes < bucket_count() + && "Hashtable is full: an error in key_equal<> or hash<>"); + } + table.set(bucknum, *it); // copies the value to here + } + settings.inc_num_ht_copies(); + } + + // Implementation is like copy_from, but it destroys the table of the + // "from" guy by freeing sparsetable memory as we iterate. This is + // useful in resizing, since we're throwing away the "from" guy anyway. + void move_from(MoveDontCopyT mover, sparse_hashtable &ht, + size_type min_buckets_wanted) { + clear(); // clear table, set num_deleted to 0 + + // If we need to change the size of our table, do it now + size_type resize_to; + if ( mover == MoveDontGrow ) + resize_to = ht.bucket_count(); // keep same size as old ht + else // MoveDontCopy + resize_to = settings.min_buckets(ht.size(), min_buckets_wanted); + if ( resize_to > bucket_count() ) { // we don't have enough buckets + table.resize(resize_to); // sets the number of buckets + settings.reset_thresholds(bucket_count()); + } + + // We use a normal iterator to get non-deleted bcks from ht + // We could use insert() here, but since we know there are + // no duplicates and no deleted items, we can be more efficient + assert( (bucket_count() & (bucket_count()-1)) == 0); // a power of two + // THIS IS THE MAJOR LINE THAT DIFFERS FROM COPY_FROM(): + for ( destructive_iterator it = ht.destructive_begin(); + it != ht.destructive_end(); ++it ) { + size_type num_probes = 0; // how many times we've probed + size_type bucknum; + for ( bucknum = hash(get_key(*it)) & (bucket_count()-1); // h % buck_cnt + table.test(bucknum); // not empty + bucknum = (bucknum + JUMP_(key, num_probes)) & (bucket_count()-1) ) { + ++num_probes; + assert(num_probes < bucket_count() + && "Hashtable is full: an error in key_equal<> or hash<>"); + } + table.set(bucknum, *it); // copies the value to here + } + settings.inc_num_ht_copies(); + } + + + // Required by the spec for hashed associative container + public: + // Though the docs say this should be num_buckets, I think it's much + // more useful as num_elements. As a special feature, calling with + // req_elements==0 will cause us to shrink if we can, saving space. + void resize(size_type req_elements) { // resize to this or larger + if ( settings.consider_shrink() || req_elements == 0 ) + maybe_shrink(); + if ( req_elements > table.num_nonempty() ) // we only grow + resize_delta(req_elements - table.num_nonempty()); + } + + // Get and change the value of shrink_factor and enlarge_factor. The + // description at the beginning of this file explains how to choose + // the values. Setting the shrink parameter to 0.0 ensures that the + // table never shrinks. + void get_resizing_parameters(float* shrink, float* grow) const { + *shrink = settings.shrink_factor(); + *grow = settings.enlarge_factor(); + } + void set_resizing_parameters(float shrink, float grow) { + settings.set_resizing_parameters(shrink, grow); + settings.reset_thresholds(bucket_count()); + } + + // CONSTRUCTORS -- as required by the specs, we take a size, + // but also let you specify a hashfunction, key comparator, + // and key extractor. We also define a copy constructor and =. + // DESTRUCTOR -- the default is fine, surprisingly. + explicit sparse_hashtable(size_type expected_max_items_in_table = 0, + const HashFcn& hf = HashFcn(), + const EqualKey& eql = EqualKey(), + const ExtractKey& ext = ExtractKey(), + const SetKey& set = SetKey(), + const Alloc& alloc = Alloc()) + : settings(hf), + key_info(ext, set, eql), + num_deleted(0), + table((expected_max_items_in_table == 0 + ? HT_DEFAULT_STARTING_BUCKETS + : settings.min_buckets(expected_max_items_in_table, 0)), + alloc) { + settings.reset_thresholds(bucket_count()); + } + + // As a convenience for resize(), we allow an optional second argument + // which lets you make this new hashtable a different size than ht. + // We also provide a mechanism of saying you want to "move" the ht argument + // into us instead of copying. + sparse_hashtable(const sparse_hashtable& ht, + size_type min_buckets_wanted = HT_DEFAULT_STARTING_BUCKETS) + : settings(ht.settings), + key_info(ht.key_info), + num_deleted(0), + table(0, ht.get_allocator()) { + settings.reset_thresholds(bucket_count()); + copy_from(ht, min_buckets_wanted); // copy_from() ignores deleted entries + } + sparse_hashtable(MoveDontCopyT mover, sparse_hashtable& ht, + size_type min_buckets_wanted = HT_DEFAULT_STARTING_BUCKETS) + : settings(ht.settings), + key_info(ht.key_info), + num_deleted(0), + table(0, ht.get_allocator()) { + settings.reset_thresholds(bucket_count()); + move_from(mover, ht, min_buckets_wanted); // ignores deleted entries + } + + sparse_hashtable& operator= (const sparse_hashtable& ht) { + if (&ht == this) return *this; // don't copy onto ourselves + settings = ht.settings; + key_info = ht.key_info; + num_deleted = ht.num_deleted; + // copy_from() calls clear and sets num_deleted to 0 too + copy_from(ht, HT_MIN_BUCKETS); + // we purposefully don't copy the allocator, which may not be copyable + return *this; + } + + // Many STL algorithms use swap instead of copy constructors + void swap(sparse_hashtable& ht) { + STL_NAMESPACE::swap(settings, ht.settings); + STL_NAMESPACE::swap(key_info, ht.key_info); + STL_NAMESPACE::swap(num_deleted, ht.num_deleted); + table.swap(ht.table); + } + + // It's always nice to be able to clear a table without deallocating it + void clear() { + if (!empty() || (num_deleted != 0)) { + table.clear(); + } + settings.reset_thresholds(bucket_count()); + num_deleted = 0; + } + + // LOOKUP ROUTINES + private: + // Returns a pair of positions: 1st where the object is, 2nd where + // it would go if you wanted to insert it. 1st is ILLEGAL_BUCKET + // if object is not found; 2nd is ILLEGAL_BUCKET if it is. + // Note: because of deletions where-to-insert is not trivial: it's the + // first deleted bucket we see, as long as we don't find the key later + pair find_position(const key_type &key) const { + size_type num_probes = 0; // how many times we've probed + const size_type bucket_count_minus_one = bucket_count() - 1; + size_type bucknum = hash(key) & bucket_count_minus_one; + size_type insert_pos = ILLEGAL_BUCKET; // where we would insert + SPARSEHASH_STAT_UPDATE(total_lookups += 1); + while ( 1 ) { // probe until something happens + if ( !table.test(bucknum) ) { // bucket is empty + SPARSEHASH_STAT_UPDATE(total_probes += num_probes); + if ( insert_pos == ILLEGAL_BUCKET ) // found no prior place to insert + return pair(ILLEGAL_BUCKET, bucknum); + else + return pair(ILLEGAL_BUCKET, insert_pos); + + } else if ( test_deleted(bucknum) ) {// keep searching, but mark to insert + if ( insert_pos == ILLEGAL_BUCKET ) + insert_pos = bucknum; + + } else if ( equals(key, get_key(table.unsafe_get(bucknum))) ) { + SPARSEHASH_STAT_UPDATE(total_probes += num_probes); + return pair(bucknum, ILLEGAL_BUCKET); + } + ++num_probes; // we're doing another probe + bucknum = (bucknum + JUMP_(key, num_probes)) & bucket_count_minus_one; + assert(num_probes < bucket_count() + && "Hashtable is full: an error in key_equal<> or hash<>"); + } + } + + public: + iterator find(const key_type& key) { + if ( size() == 0 ) return end(); + pair pos = find_position(key); + if ( pos.first == ILLEGAL_BUCKET ) // alas, not there + return end(); + else + return iterator(this, table.get_iter(pos.first), table.nonempty_end()); + } + + const_iterator find(const key_type& key) const { + if ( size() == 0 ) return end(); + pair pos = find_position(key); + if ( pos.first == ILLEGAL_BUCKET ) // alas, not there + return end(); + else + return const_iterator(this, + table.get_iter(pos.first), table.nonempty_end()); + } + + // This is a tr1 method: the bucket a given key is in, or what bucket + // it would be put in, if it were to be inserted. Shrug. + size_type bucket(const key_type& key) const { + pair pos = find_position(key); + return pos.first == ILLEGAL_BUCKET ? pos.second : pos.first; + } + + // Counts how many elements have key key. For maps, it's either 0 or 1. + size_type count(const key_type &key) const { + pair pos = find_position(key); + return pos.first == ILLEGAL_BUCKET ? 0 : 1; + } + + // Likewise, equal_range doesn't really make sense for us. Oh well. + pair equal_range(const key_type& key) { + iterator pos = find(key); // either an iterator or end + if (pos == end()) { + return pair(pos, pos); + } else { + const iterator startpos = pos++; + return pair(startpos, pos); + } + } + pair equal_range(const key_type& key) const { + const_iterator pos = find(key); // either an iterator or end + if (pos == end()) { + return pair(pos, pos); + } else { + const const_iterator startpos = pos++; + return pair(startpos, pos); + } + } + + + // INSERTION ROUTINES + private: + // Private method used by insert_noresize and find_or_insert. + iterator insert_at(const_reference obj, size_type pos) { + if (size() >= max_size()) + throw std::length_error("insert overflow"); + if ( test_deleted(pos) ) { // just replace if it's been deleted + // The set() below will undelete this object. We just worry about stats + assert(num_deleted > 0); + --num_deleted; // used to be, now it isn't + } + table.set(pos, obj); + return iterator(this, table.get_iter(pos), table.nonempty_end()); + } + + // If you know *this is big enough to hold obj, use this routine + pair insert_noresize(const_reference obj) { + // First, double-check we're not inserting delkey + assert((!settings.use_deleted() || !equals(get_key(obj), key_info.delkey)) + && "Inserting the deleted key"); + const pair pos = find_position(get_key(obj)); + if ( pos.first != ILLEGAL_BUCKET) { // object was already there + return pair(iterator(this, table.get_iter(pos.first), + table.nonempty_end()), + false); // false: we didn't insert + } else { // pos.second says where to put it + return pair(insert_at(obj, pos.second), true); + } + } + + // Specializations of insert(it, it) depending on the power of the iterator: + // (1) Iterator supports operator-, resize before inserting + template + void insert(ForwardIterator f, ForwardIterator l, STL_NAMESPACE::forward_iterator_tag) { + size_t dist = STL_NAMESPACE::distance(f, l); + if (dist >= (std::numeric_limits::max)()) + throw std::length_error("insert-range overflow"); + resize_delta(static_cast(dist)); + for ( ; dist > 0; --dist, ++f) { + insert_noresize(*f); + } + } + + // (2) Arbitrary iterator, can't tell how much to resize + template + void insert(InputIterator f, InputIterator l, STL_NAMESPACE::input_iterator_tag) { + for ( ; f != l; ++f) + insert(*f); + } + + public: + // This is the normal insert routine, used by the outside world + pair insert(const_reference obj) { + resize_delta(1); // adding an object, grow if need be + return insert_noresize(obj); + } + + // When inserting a lot at a time, we specialize on the type of iterator + template + void insert(InputIterator f, InputIterator l) { + // specializes on iterator type + insert(f, l, typename STL_NAMESPACE::iterator_traits::iterator_category()); + } + + // DefaultValue is a functor that takes a key and returns a value_type + // representing the default value to be inserted if none is found. + template + value_type& find_or_insert(const key_type& key) { + // First, double-check we're not inserting delkey + assert((!settings.use_deleted() || !equals(key, key_info.delkey)) + && "Inserting the deleted key"); + const pair pos = find_position(key); + DefaultValue default_value; + if ( pos.first != ILLEGAL_BUCKET) { // object was already there + return *table.get_iter(pos.first); + } else if (resize_delta(1)) { // needed to rehash to make room + // Since we resized, we can't use pos, so recalculate where to insert. + return *insert_noresize(default_value(key)).first; + } else { // no need to rehash, insert right here + return *insert_at(default_value(key), pos.second); + } + } + + // DELETION ROUTINES + size_type erase(const key_type& key) { + // First, double-check we're not erasing delkey. + assert((!settings.use_deleted() || !equals(key, key_info.delkey)) + && "Erasing the deleted key"); + assert(!settings.use_deleted() || !equals(key, key_info.delkey)); + const_iterator pos = find(key); // shrug: shouldn't need to be const + if ( pos != end() ) { + assert(!test_deleted(pos)); // or find() shouldn't have returned it + set_deleted(pos); + ++num_deleted; + // will think about shrink after next insert + settings.set_consider_shrink(true); + return 1; // because we deleted one thing + } else { + return 0; // because we deleted nothing + } + } + + // We return the iterator past the deleted item. + void erase(iterator pos) { + if ( pos == end() ) return; // sanity check + if ( set_deleted(pos) ) { // true if object has been newly deleted + ++num_deleted; + // will think about shrink after next insert + settings.set_consider_shrink(true); + } + } + + void erase(iterator f, iterator l) { + for ( ; f != l; ++f) { + if ( set_deleted(f) ) // should always be true + ++num_deleted; + } + // will think about shrink after next insert + settings.set_consider_shrink(true); + } + + // We allow you to erase a const_iterator just like we allow you to + // erase an iterator. This is in parallel to 'delete': you can delete + // a const pointer just like a non-const pointer. The logic is that + // you can't use the object after it's erased anyway, so it doesn't matter + // if it's const or not. + void erase(const_iterator pos) { + if ( pos == end() ) return; // sanity check + if ( set_deleted(pos) ) { // true if object has been newly deleted + ++num_deleted; + // will think about shrink after next insert + settings.set_consider_shrink(true); + } + } + void erase(const_iterator f, const_iterator l) { + for ( ; f != l; ++f) { + if ( set_deleted(f) ) // should always be true + ++num_deleted; + } + // will think about shrink after next insert + settings.set_consider_shrink(true); + } + + + // COMPARISON + bool operator==(const sparse_hashtable& ht) const { + if (size() != ht.size()) { + return false; + } else if (this == &ht) { + return true; + } else { + // Iterate through the elements in "this" and see if the + // corresponding element is in ht + for ( const_iterator it = begin(); it != end(); ++it ) { + const_iterator it2 = ht.find(get_key(*it)); + if ((it2 == ht.end()) || (*it != *it2)) { + return false; + } + } + return true; + } + } + bool operator!=(const sparse_hashtable& ht) const { + return !(*this == ht); + } + + + // I/O + // We support reading and writing hashtables to disk. NOTE that + // this only stores the hashtable metadata, not the stuff you've + // actually put in the hashtable! Alas, since I don't know how to + // write a hasher or key_equal, you have to make sure everything + // but the table is the same. We compact before writing. + bool write_metadata(FILE *fp) { + squash_deleted(); // so we don't have to worry about delkey + return table.write_metadata(fp); + } + + bool read_metadata(FILE *fp) { + num_deleted = 0; // since we got rid before writing + bool result = table.read_metadata(fp); + settings.reset_thresholds(bucket_count()); + return result; + } + + // Only meaningful if value_type is a POD. + bool write_nopointer_data(FILE *fp) { + return table.write_nopointer_data(fp); + } + + // Only meaningful if value_type is a POD. + bool read_nopointer_data(FILE *fp) { + return table.read_nopointer_data(fp); + } + + private: + // Table is the main storage class. + typedef sparsetable Table; + + // Package templated functors with the other types to eliminate memory + // needed for storing these zero-size operators. Since ExtractKey and + // hasher's operator() might have the same function signature, they + // must be packaged in different classes. + struct Settings : + sh_hashtable_settings { + explicit Settings(const hasher& hf) + : sh_hashtable_settings( + hf, HT_OCCUPANCY_PCT / 100.0f, HT_EMPTY_PCT / 100.0f) {} + }; + + // KeyInfo stores delete key and packages zero-size functors: + // ExtractKey and SetKey. + class KeyInfo : public ExtractKey, public SetKey, public key_equal { + public: + KeyInfo(const ExtractKey& ek, const SetKey& sk, const key_equal& eq) + : ExtractKey(ek), + SetKey(sk), + key_equal(eq) { + } + // We want to return the exact same type as ExtractKey: Key or const Key& + typename ExtractKey::result_type get_key(const_reference v) const { + return ExtractKey::operator()(v); + } + void set_key(pointer v, const key_type& k) const { + SetKey::operator()(v, k); + } + bool equals(const key_type& a, const key_type& b) const { + return key_equal::operator()(a, b); + } + + // Which key marks deleted entries. + // TODO(csilvers): make a pointer, and get rid of use_deleted (benchmark!) + typename remove_const::type delkey; + }; + + // Utility functions to access the templated operators + size_type hash(const key_type& v) const { + return settings.hash(v); + } + bool equals(const key_type& a, const key_type& b) const { + return key_info.equals(a, b); + } + typename ExtractKey::result_type get_key(const_reference v) const { + return key_info.get_key(v); + } + void set_key(pointer v, const key_type& k) const { + key_info.set_key(v, k); + } + + private: + // Actual data + Settings settings; + KeyInfo key_info; + size_type num_deleted; // how many occupied buckets are marked deleted + Table table; // holds num_buckets and num_elements too +}; + + +// We need a global swap as well +template +inline void swap(sparse_hashtable &x, + sparse_hashtable &y) { + x.swap(y); +} + +#undef JUMP_ + +template +const typename sparse_hashtable::size_type + sparse_hashtable::ILLEGAL_BUCKET; + +// How full we let the table get before we resize. Knuth says .8 is +// good -- higher causes us to probe too much, though saves memory +template +const int sparse_hashtable::HT_OCCUPANCY_PCT = 80; + +// How empty we let the table get before we resize lower. +// It should be less than OCCUPANCY_PCT / 2 or we thrash resizing +template +const int sparse_hashtable::HT_EMPTY_PCT + = static_cast(0.4 * + sparse_hashtable::HT_OCCUPANCY_PCT); + +_END_GOOGLE_NAMESPACE_ + +#endif /* _SPARSEHASHTABLE_H_ */ diff --git a/external/google/sparsetable b/external/google/sparsetable new file mode 100644 index 00000000..aa4ccab4 --- /dev/null +++ b/external/google/sparsetable @@ -0,0 +1,1598 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// --- +// Author: Craig Silverstein +// +// A sparsetable is a random container that implements a sparse array, +// that is, an array that uses very little memory to store unassigned +// indices (in this case, between 1-2 bits per unassigned index). For +// instance, if you allocate an array of size 5 and assign a[2] = , then a[2] will take up a lot of memory but a[0], a[1], +// a[3], and a[4] will not. Array elements that have a value are +// called "assigned". Array elements that have no value yet, or have +// had their value cleared using erase() or clear(), are called +// "unassigned". +// +// Unassigned values seem to have the default value of T (see below). +// Nevertheless, there is a difference between an unassigned index and +// one explicitly assigned the value of T(). The latter is considered +// assigned. +// +// Access to an array element is constant time, as is insertion and +// deletion. Insertion and deletion may be fairly slow, however: +// because of this container's memory economy, each insert and delete +// causes a memory reallocation. +// +// See doc/sparsetable.html for information about how to use this class. + +#ifndef _SPARSETABLE_H_ +#define _SPARSETABLE_H_ + +#include +#include // for malloc/free +#include // to read/write tables +#ifdef HAVE_STDINT_H +#include // the normal place uint16_t is defined +#endif +#ifdef HAVE_SYS_TYPES_H +#include // the normal place u_int16_t is defined +#endif +#ifdef HAVE_INTTYPES_H +#include // a third place for uint16_t or u_int16_t +#endif +#include // for bounds checking +#include // to define reverse_iterator for me +#include // equal, lexicographical_compare, swap,... +#include // uninitialized_copy +#include // a sparsetable is a vector of groups +#include +#include // for true_type, integral_constant, etc. + +#if STDC_HEADERS +#include // for memcpy +#else +#if !HAVE_MEMCPY +#define memcpy(d, s, n) bcopy ((s), (d), (n)) +#endif +#endif + +#ifndef HAVE_U_INT16_T +# if defined HAVE_UINT16_T + typedef uint16_t u_int16_t; // true on solaris, possibly other C99 libc's +# elif defined HAVE___UINT16 + typedef __int16 int16_t; // true on vc++7 + typedef unsigned __int16 u_int16_t; +# else + // Cannot find a 16-bit integer type. Hoping for the best with "short"... + typedef short int int16_t; + typedef unsigned short int u_int16_t; +# endif +#endif + +_START_GOOGLE_NAMESPACE_ + +using STL_NAMESPACE::vector; +using STL_NAMESPACE::uninitialized_copy; + +// The smaller this is, the faster lookup is (because the group bitmap is +// smaller) and the faster insert is, because there's less to move. +// On the other hand, there are more groups. Since group::size_type is +// a short, this number should be of the form 32*x + 16 to avoid waste. +static const u_int16_t DEFAULT_SPARSEGROUP_SIZE = 48; // fits in 1.5 words + + +// A NOTE ON ASSIGNING: +// A sparse table does not actually allocate memory for entries +// that are not filled. Because of this, it becomes complicated +// to have a non-const iterator: we don't know, if the iterator points +// to a not-filled bucket, whether you plan to fill it with something +// or whether you plan to read its value (in which case you'll get +// the default bucket value). Therefore, while we can define const +// operations in a pretty 'normal' way, for non-const operations, we +// define something that returns a helper object with operator= and +// operator& that allocate a bucket lazily. We use this for table[] +// and also for regular table iterators. + +template +class table_element_adaptor { + public: + typedef typename tabletype::value_type value_type; + typedef typename tabletype::size_type size_type; + typedef typename tabletype::reference reference; + typedef typename tabletype::pointer pointer; + + table_element_adaptor(tabletype *tbl, size_type p) + : table(tbl), pos(p) { } + table_element_adaptor& operator= (const value_type &val) { + table->set(pos, val); + return *this; + } + operator value_type() { return table->get(pos); } // we look like a value + pointer operator& () { return &table->mutating_get(pos); } + + private: + tabletype* table; + size_type pos; +}; + +// Our iterator as simple as iterators can be: basically it's just +// the index into our table. Dereference, the only complicated +// thing, we punt to the table class. This just goes to show how +// much machinery STL requires to do even the most trivial tasks. +// +// By templatizing over tabletype, we have one iterator type which +// we can use for both sparsetables and sparsebins. In fact it +// works on any class that allows size() and operator[] (eg vector), +// as long as it does the standard STL typedefs too (eg value_type). + +template +class table_iterator { + public: + typedef table_iterator iterator; + + typedef STL_NAMESPACE::random_access_iterator_tag iterator_category; + typedef typename tabletype::value_type value_type; + typedef typename tabletype::difference_type difference_type; + typedef typename tabletype::size_type size_type; + typedef table_element_adaptor reference; + typedef table_element_adaptor* pointer; + + // The "real" constructor + table_iterator(tabletype *tbl, size_type p) + : table(tbl), pos(p) { } + // The default constructor, used when I define vars of type table::iterator + table_iterator() : table(NULL), pos(0) { } + // The copy constructor, for when I say table::iterator foo = tbl.begin() + // The default destructor is fine; we don't define one + // The default operator= is fine; we don't define one + + // The main thing our iterator does is dereference. If the table entry + // we point to is empty, we return the default value type. + // This is the big different function from the const iterator. + reference operator*() { + return table_element_adaptor(table, pos); + } + pointer operator->() { return &(operator*()); } + + // Helper function to assert things are ok; eg pos is still in range + void check() const { + assert(table); + assert(pos <= table->size()); + } + + // Arithmetic: we just do arithmetic on pos. We don't even need to + // do bounds checking, since STL doesn't consider that its job. :-) + iterator& operator+=(size_type t) { pos += t; check(); return *this; } + iterator& operator-=(size_type t) { pos -= t; check(); return *this; } + iterator& operator++() { ++pos; check(); return *this; } + iterator& operator--() { --pos; check(); return *this; } + iterator operator++(int) { iterator tmp(*this); // for x++ + ++pos; check(); return tmp; } + iterator operator--(int) { iterator tmp(*this); // for x-- + --pos; check(); return tmp; } + iterator operator+(difference_type i) const { iterator tmp(*this); + tmp += i; return tmp; } + iterator operator-(difference_type i) const { iterator tmp(*this); + tmp -= i; return tmp; } + difference_type operator-(iterator it) const { // for "x = it2 - it" + assert(table == it.table); + return pos - it.pos; + } + reference operator[](difference_type n) const { + return *(*this + n); // simple though not totally efficient + } + + // Comparisons. + bool operator==(const iterator& it) const { + return table == it.table && pos == it.pos; + } + bool operator<(const iterator& it) const { + assert(table == it.table); // life is bad bad bad otherwise + return pos < it.pos; + } + bool operator!=(const iterator& it) const { return !(*this == it); } + bool operator<=(const iterator& it) const { return !(it < *this); } + bool operator>(const iterator& it) const { return it < *this; } + bool operator>=(const iterator& it) const { return !(*this < it); } + + // Here's the info we actually need to be an iterator + tabletype *table; // so we can dereference and bounds-check + size_type pos; // index into the table +}; + +// support for "3 + iterator" has to be defined outside the class, alas +template +table_iterator operator+(typename table_iterator::difference_type i, + table_iterator it) { + return it + i; // so people can say it2 = 3 + it +} + +template +class const_table_iterator { + public: + typedef table_iterator iterator; + typedef const_table_iterator const_iterator; + + typedef STL_NAMESPACE::random_access_iterator_tag iterator_category; + typedef typename tabletype::value_type value_type; + typedef typename tabletype::difference_type difference_type; + typedef typename tabletype::size_type size_type; + typedef typename tabletype::const_reference reference; // we're const-only + typedef typename tabletype::const_pointer pointer; + + // The "real" constructor + const_table_iterator(const tabletype *tbl, size_type p) + : table(tbl), pos(p) { } + // The default constructor, used when I define vars of type table::iterator + const_table_iterator() : table(NULL), pos(0) { } + // The copy constructor, for when I say table::iterator foo = tbl.begin() + // Also converts normal iterators to const iterators + const_table_iterator(const iterator &from) + : table(from.table), pos(from.pos) { } + // The default destructor is fine; we don't define one + // The default operator= is fine; we don't define one + + // The main thing our iterator does is dereference. If the table entry + // we point to is empty, we return the default value type. + reference operator*() const { return (*table)[pos]; } + pointer operator->() const { return &(operator*()); } + + // Helper function to assert things are ok; eg pos is still in range + void check() const { + assert(table); + assert(pos <= table->size()); + } + + // Arithmetic: we just do arithmetic on pos. We don't even need to + // do bounds checking, since STL doesn't consider that its job. :-) + const_iterator& operator+=(size_type t) { pos += t; check(); return *this; } + const_iterator& operator-=(size_type t) { pos -= t; check(); return *this; } + const_iterator& operator++() { ++pos; check(); return *this; } + const_iterator& operator--() { --pos; check(); return *this; } + const_iterator operator++(int) { const_iterator tmp(*this); // for x++ + ++pos; check(); return tmp; } + const_iterator operator--(int) { const_iterator tmp(*this); // for x-- + --pos; check(); return tmp; } + const_iterator operator+(difference_type i) const { const_iterator tmp(*this); + tmp += i; return tmp; } + const_iterator operator-(difference_type i) const { const_iterator tmp(*this); + tmp -= i; return tmp; } + difference_type operator-(const_iterator it) const { // for "x = it2 - it" + assert(table == it.table); + return pos - it.pos; + } + reference operator[](difference_type n) const { + return *(*this + n); // simple though not totally efficient + } + + // Comparisons. + bool operator==(const const_iterator& it) const { + return table == it.table && pos == it.pos; + } + bool operator<(const const_iterator& it) const { + assert(table == it.table); // life is bad bad bad otherwise + return pos < it.pos; + } + bool operator!=(const const_iterator& it) const { return !(*this == it); } + bool operator<=(const const_iterator& it) const { return !(it < *this); } + bool operator>(const const_iterator& it) const { return it < *this; } + bool operator>=(const const_iterator& it) const { return !(*this < it); } + + // Here's the info we actually need to be an iterator + const tabletype *table; // so we can dereference and bounds-check + size_type pos; // index into the table +}; + +// support for "3 + iterator" has to be defined outside the class, alas +template +const_table_iterator operator+(typename + const_table_iterator::difference_type i, + const_table_iterator it) { + return it + i; // so people can say it2 = 3 + it +} + + +// --------------------------------------------------------------------------- + + +/* +// This is a 2-D iterator. You specify a begin and end over a list +// of *containers*. We iterate over each container by iterating over +// it. It's actually simple: +// VECTOR.begin() VECTOR[0].begin() --------> VECTOR[0].end() ---, +// | ________________________________________________/ +// | \_> VECTOR[1].begin() --------> VECTOR[1].end() -, +// | ___________________________________________________/ +// v \_> ...... +// VECTOR.end() +// +// It's impossible to do random access on one of these things in constant +// time, so it's just a bidirectional iterator. +// +// Unfortunately, because we need to use this for a non-empty iterator, +// we use nonempty_begin() and nonempty_end() instead of begin() and end() +// (though only going across, not down). +*/ + +#define TWOD_BEGIN_ nonempty_begin +#define TWOD_END_ nonempty_end +#define TWOD_ITER_ nonempty_iterator +#define TWOD_CONST_ITER_ const_nonempty_iterator + +template +class two_d_iterator { + public: + typedef two_d_iterator iterator; + + typedef STL_NAMESPACE::bidirectional_iterator_tag iterator_category; + // apparently some versions of VC++ have trouble with two ::'s in a typename + typedef typename containertype::value_type _tmp_vt; + typedef typename _tmp_vt::value_type value_type; + typedef typename _tmp_vt::difference_type difference_type; + typedef typename _tmp_vt::reference reference; + typedef typename _tmp_vt::pointer pointer; + + // The "real" constructor. begin and end specify how many rows we have + // (in the diagram above); we always iterate over each row completely. + two_d_iterator(typename containertype::iterator begin, + typename containertype::iterator end, + typename containertype::iterator curr) + : row_begin(begin), row_end(end), row_current(curr), col_current() { + if ( row_current != row_end ) { + col_current = row_current->TWOD_BEGIN_(); + advance_past_end(); // in case cur->begin() == cur->end() + } + } + // If you want to start at an arbitrary place, you can, I guess + two_d_iterator(typename containertype::iterator begin, + typename containertype::iterator end, + typename containertype::iterator curr, + typename containertype::value_type::TWOD_ITER_ col) + : row_begin(begin), row_end(end), row_current(curr), col_current(col) { + advance_past_end(); // in case cur->begin() == cur->end() + } + // The default constructor, used when I define vars of type table::iterator + two_d_iterator() : row_begin(), row_end(), row_current(), col_current() { } + // The default destructor is fine; we don't define one + // The default operator= is fine; we don't define one + + // Happy dereferencer + reference operator*() const { return *col_current; } + pointer operator->() const { return &(operator*()); } + + // Arithmetic: we just do arithmetic on pos. We don't even need to + // do bounds checking, since STL doesn't consider that its job. :-) + // NOTE: this is not amortized constant time! What do we do about it? + void advance_past_end() { // used when col_current points to end() + while ( col_current == row_current->TWOD_END_() ) { // end of current row + ++row_current; // go to beginning of next + if ( row_current != row_end ) // col is irrelevant at end + col_current = row_current->TWOD_BEGIN_(); + else + break; // don't go past row_end + } + } + + iterator& operator++() { + assert(row_current != row_end); // how to ++ from there? + ++col_current; + advance_past_end(); // in case col_current is at end() + return *this; + } + iterator& operator--() { + while ( row_current == row_end || + col_current == row_current->TWOD_BEGIN_() ) { + assert(row_current != row_begin); + --row_current; + col_current = row_current->TWOD_END_(); // this is 1 too far + } + --col_current; + return *this; + } + iterator operator++(int) { iterator tmp(*this); ++*this; return tmp; } + iterator operator--(int) { iterator tmp(*this); --*this; return tmp; } + + + // Comparisons. + bool operator==(const iterator& it) const { + return ( row_begin == it.row_begin && + row_end == it.row_end && + row_current == it.row_current && + (row_current == row_end || col_current == it.col_current) ); + } + bool operator!=(const iterator& it) const { return !(*this == it); } + + + // Here's the info we actually need to be an iterator + // These need to be public so we convert from iterator to const_iterator + typename containertype::iterator row_begin, row_end, row_current; + typename containertype::value_type::TWOD_ITER_ col_current; +}; + +// The same thing again, but this time const. :-( +template +class const_two_d_iterator { + public: + typedef const_two_d_iterator iterator; + + typedef STL_NAMESPACE::bidirectional_iterator_tag iterator_category; + // apparently some versions of VC++ have trouble with two ::'s in a typename + typedef typename containertype::value_type _tmp_vt; + typedef typename _tmp_vt::value_type value_type; + typedef typename _tmp_vt::difference_type difference_type; + typedef typename _tmp_vt::const_reference reference; + typedef typename _tmp_vt::const_pointer pointer; + + const_two_d_iterator(typename containertype::const_iterator begin, + typename containertype::const_iterator end, + typename containertype::const_iterator curr) + : row_begin(begin), row_end(end), row_current(curr), col_current() { + if ( curr != end ) { + col_current = curr->TWOD_BEGIN_(); + advance_past_end(); // in case cur->begin() == cur->end() + } + } + const_two_d_iterator(typename containertype::const_iterator begin, + typename containertype::const_iterator end, + typename containertype::const_iterator curr, + typename containertype::value_type::TWOD_CONST_ITER_ col) + : row_begin(begin), row_end(end), row_current(curr), col_current(col) { + advance_past_end(); // in case cur->begin() == cur->end() + } + const_two_d_iterator() + : row_begin(), row_end(), row_current(), col_current() { + } + // Need this explicitly so we can convert normal iterators to const iterators + const_two_d_iterator(const two_d_iterator& it) : + row_begin(it.row_begin), row_end(it.row_end), row_current(it.row_current), + col_current(it.col_current) { } + + typename containertype::const_iterator row_begin, row_end, row_current; + typename containertype::value_type::TWOD_CONST_ITER_ col_current; + + + // EVERYTHING FROM HERE DOWN IS THE SAME AS THE NON-CONST ITERATOR + reference operator*() const { return *col_current; } + pointer operator->() const { return &(operator*()); } + + void advance_past_end() { // used when col_current points to end() + while ( col_current == row_current->TWOD_END_() ) { // end of current row + ++row_current; // go to beginning of next + if ( row_current != row_end ) // col is irrelevant at end + col_current = row_current->TWOD_BEGIN_(); + else + break; // don't go past row_end + } + } + iterator& operator++() { + assert(row_current != row_end); // how to ++ from there? + ++col_current; + advance_past_end(); // in case col_current is at end() + return *this; + } + iterator& operator--() { + while ( row_current == row_end || + col_current == row_current->TWOD_BEGIN_() ) { + assert(row_current != row_begin); + --row_current; + col_current = row_current->TWOD_END_(); // this is 1 too far + } + --col_current; + return *this; + } + iterator operator++(int) { iterator tmp(*this); ++*this; return tmp; } + iterator operator--(int) { iterator tmp(*this); --*this; return tmp; } + + bool operator==(const iterator& it) const { + return ( row_begin == it.row_begin && + row_end == it.row_end && + row_current == it.row_current && + (row_current == row_end || col_current == it.col_current) ); + } + bool operator!=(const iterator& it) const { return !(*this == it); } +}; + +// We provide yet another version, to be as frugal with memory as +// possible. This one frees each block of memory as it finishes +// iterating over it. By the end, the entire table is freed. +// For understandable reasons, you can only iterate over it once, +// which is why it's an input iterator +template +class destructive_two_d_iterator { + public: + typedef destructive_two_d_iterator iterator; + + typedef STL_NAMESPACE::input_iterator_tag iterator_category; + // apparently some versions of VC++ have trouble with two ::'s in a typename + typedef typename containertype::value_type _tmp_vt; + typedef typename _tmp_vt::value_type value_type; + typedef typename _tmp_vt::difference_type difference_type; + typedef typename _tmp_vt::reference reference; + typedef typename _tmp_vt::pointer pointer; + + destructive_two_d_iterator(typename containertype::iterator begin, + typename containertype::iterator end, + typename containertype::iterator curr) + : row_begin(begin), row_end(end), row_current(curr), col_current() { + if ( curr != end ) { + col_current = curr->TWOD_BEGIN_(); + advance_past_end(); // in case cur->begin() == cur->end() + } + } + destructive_two_d_iterator(typename containertype::iterator begin, + typename containertype::iterator end, + typename containertype::iterator curr, + typename containertype::value_type::TWOD_ITER_ col) + : row_begin(begin), row_end(end), row_current(curr), col_current(col) { + advance_past_end(); // in case cur->begin() == cur->end() + } + destructive_two_d_iterator() + : row_begin(), row_end(), row_current(), col_current() { + } + + typename containertype::iterator row_begin, row_end, row_current; + typename containertype::value_type::TWOD_ITER_ col_current; + + // This is the part that destroys + void advance_past_end() { // used when col_current points to end() + while ( col_current == row_current->TWOD_END_() ) { // end of current row + row_current->clear(); // the destructive part + // It would be nice if we could decrement sparsetable->num_buckets here + ++row_current; // go to beginning of next + if ( row_current != row_end ) // col is irrelevant at end + col_current = row_current->TWOD_BEGIN_(); + else + break; // don't go past row_end + } + } + + // EVERYTHING FROM HERE DOWN IS THE SAME AS THE REGULAR ITERATOR + reference operator*() const { return *col_current; } + pointer operator->() const { return &(operator*()); } + + iterator& operator++() { + assert(row_current != row_end); // how to ++ from there? + ++col_current; + advance_past_end(); // in case col_current is at end() + return *this; + } + iterator operator++(int) { iterator tmp(*this); ++*this; return tmp; } + + bool operator==(const iterator& it) const { + return ( row_begin == it.row_begin && + row_end == it.row_end && + row_current == it.row_current && + (row_current == row_end || col_current == it.col_current) ); + } + bool operator!=(const iterator& it) const { return !(*this == it); } +}; + +#undef TWOD_BEGIN_ +#undef TWOD_END_ +#undef TWOD_ITER_ +#undef TWOD_CONST_ITER_ + + + + +// SPARSE-TABLE +// ------------ +// The idea is that a table with (logically) t buckets is divided +// into t/M *groups* of M buckets each. (M is a constant set in +// GROUP_SIZE for efficiency.) Each group is stored sparsely. +// Thus, inserting into the table causes some array to grow, which is +// slow but still constant time. Lookup involves doing a +// logical-position-to-sparse-position lookup, which is also slow but +// constant time. The larger M is, the slower these operations are +// but the less overhead (slightly). +// +// To store the sparse array, we store a bitmap B, where B[i] = 1 iff +// bucket i is non-empty. Then to look up bucket i we really look up +// array[# of 1s before i in B]. This is constant time for fixed M. +// +// Terminology: the position of an item in the overall table (from +// 1 .. t) is called its "location." The logical position in a group +// (from 1 .. M ) is called its "position." The actual location in +// the array (from 1 .. # of non-empty buckets in the group) is +// called its "offset." + +// The weird mod in the offset is entirely to quiet compiler warnings +// as is the cast to int after doing the "x mod 256" +#define PUT_(take_from, offset) do { \ + if (putc(static_cast(((take_from) >> ((offset) % (sizeof(take_from)*8)))\ + % 256), fp) \ + == EOF) \ + return false; \ +} while (0) + +#define GET_(add_to, offset) do { \ + if ((x=getc(fp)) == EOF) \ + return false; \ + else \ + add_to |= (static_cast(x) << ((offset) % (sizeof(add_to)*8))); \ +} while (0) + +template +class sparsegroup { + private: + typedef typename Alloc::template rebind::other value_alloc_type; + + public: + // Basic types + typedef T value_type; + typedef Alloc allocator_type; + typedef typename value_alloc_type::reference reference; + typedef typename value_alloc_type::const_reference const_reference; + typedef typename value_alloc_type::pointer pointer; + typedef typename value_alloc_type::const_pointer const_pointer; + + typedef table_iterator > iterator; + typedef const_table_iterator > + const_iterator; + typedef table_element_adaptor > + element_adaptor; + typedef u_int16_t size_type; // max # of buckets + typedef int16_t difference_type; + typedef STL_NAMESPACE::reverse_iterator const_reverse_iterator; + typedef STL_NAMESPACE::reverse_iterator reverse_iterator; + + // These are our special iterators, that go over non-empty buckets in a + // group. These aren't const-only because you can change non-empty bcks. + typedef pointer nonempty_iterator; + typedef const_pointer const_nonempty_iterator; + typedef STL_NAMESPACE::reverse_iterator reverse_nonempty_iterator; + typedef STL_NAMESPACE::reverse_iterator const_reverse_nonempty_iterator; + + // Iterator functions + iterator begin() { return iterator(this, 0); } + const_iterator begin() const { return const_iterator(this, 0); } + iterator end() { return iterator(this, size()); } + const_iterator end() const { return const_iterator(this, size()); } + reverse_iterator rbegin() { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } + reverse_iterator rend() { return reverse_iterator(begin()); } + const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } + + // We'll have versions for our special non-empty iterator too + nonempty_iterator nonempty_begin() { return group; } + const_nonempty_iterator nonempty_begin() const { return group; } + nonempty_iterator nonempty_end() { + return group + settings.num_buckets; + } + const_nonempty_iterator nonempty_end() const { + return group + settings.num_buckets; + } + reverse_nonempty_iterator nonempty_rbegin() { + return reverse_nonempty_iterator(nonempty_end()); + } + const_reverse_nonempty_iterator nonempty_rbegin() const { + return const_reverse_nonempty_iterator(nonempty_end()); + } + reverse_nonempty_iterator nonempty_rend() { + return reverse_nonempty_iterator(nonempty_begin()); + } + const_reverse_nonempty_iterator nonempty_rend() const { + return const_reverse_nonempty_iterator(nonempty_begin()); + } + + + // This gives us the "default" value to return for an empty bucket. + // We just use the default constructor on T, the template type + const_reference default_value() const { + static value_type defaultval = value_type(); + return defaultval; + } + + + private: + // We need to do all this bit manipulation, of course. ick + static size_type charbit(size_type i) { return i >> 3; } + static size_type modbit(size_type i) { return 1 << (i&7); } + int bmtest(size_type i) const { return bitmap[charbit(i)] & modbit(i); } + void bmset(size_type i) { bitmap[charbit(i)] |= modbit(i); } + void bmclear(size_type i) { bitmap[charbit(i)] &= ~modbit(i); } + + pointer allocate_group(size_type n) { + pointer retval = settings.allocate(n); + if (retval == NULL) { + // We really should use PRIuS here, but I don't want to have to add + // a whole new configure option, with concomitant macro namespace + // pollution, just to print this (unlikely) error message. So I cast. + fprintf(stderr, "sparsehash: FATAL ERROR: " + "failed to allocate %lu groups\n", + static_cast(n)); + exit(1); + } + return retval; + } + + void free_group() { + if (!group) return; + pointer end_it = group + settings.num_buckets; + for (pointer p = group; p != end_it; ++p) + p->~value_type(); + settings.deallocate(group, settings.num_buckets); + group = NULL; + } + + public: // get_iter() in sparsetable needs it + // We need a small function that tells us how many set bits there are + // in positions 0..i-1 of the bitmap. It uses a big table. + // We make it static so templates don't allocate lots of these tables. + // There are lots of ways to do this calculation (called 'popcount'). + // The 8-bit table lookup is one of the fastest, though this + // implementation suffers from not doing any loop unrolling. See, eg, + // http://www.dalkescientific.com/writings/diary/archive/2008/07/03/hakmem_and_other_popcounts.html + // http://gurmeetsingh.wordpress.com/2008/08/05/fast-bit-counting-routines/ + static size_type pos_to_offset(const unsigned char *bm, size_type pos) { + // We could make these ints. The tradeoff is size (eg does it overwhelm + // the cache?) vs efficiency in referencing sub-word-sized array elements + static const char bits_in[256] = { // # of bits set in one char + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, + }; + size_type retval = 0; + + // [Note: condition pos > 8 is an optimization; convince yourself we + // give exactly the same result as if we had pos >= 8 here instead.] + for ( ; pos > 8; pos -= 8 ) // bm[0..pos/8-1] + retval += bits_in[*bm++]; // chars we want *all* bits in + return retval + bits_in[*bm & ((1 << pos)-1)]; // the char that includes pos + } + + size_type pos_to_offset(size_type pos) const { // not static but still const + return pos_to_offset(bitmap, pos); + } + + + public: + // Constructors -- default and copy -- and destructor + sparsegroup(allocator_type& a) : + group(0), settings(alloc_impl(a)) { + memset(bitmap, 0, sizeof(bitmap)); + } + sparsegroup(const sparsegroup& x) : group(0), settings(x.settings) { + if ( settings.num_buckets ) { + group = allocate_group(x.settings.num_buckets); + uninitialized_copy(x.group, x.group + x.settings.num_buckets, group); + } + memcpy(bitmap, x.bitmap, sizeof(bitmap)); + } + ~sparsegroup() { free_group(); } + + // Operator= is just like the copy constructor, I guess + // TODO(austern): Make this exception safe. Handle exceptions in value_type's + // copy constructor. + sparsegroup &operator=(const sparsegroup& x) { + if ( &x == this ) return *this; // x = x + if ( x.settings.num_buckets == 0 ) { + free_group(); + } else { + pointer p = allocate_group(x.settings.num_buckets); + uninitialized_copy(x.group, x.group + x.settings.num_buckets, p); + free_group(); + group = p; + } + memcpy(bitmap, x.bitmap, sizeof(bitmap)); + settings.num_buckets = x.settings.num_buckets; + return *this; + } + + // Many STL algorithms use swap instead of copy constructors + void swap(sparsegroup& x) { + STL_NAMESPACE::swap(group, x.group); + for ( int i = 0; i < sizeof(bitmap) / sizeof(*bitmap); ++i ) + STL_NAMESPACE::swap(bitmap[i], x.bitmap[i]); // swap not defined on arrays + STL_NAMESPACE::swap(settings.num_buckets, x.settings.num_buckets); + // we purposefully don't swap the allocator, which may not be swap-able + } + + // It's always nice to be able to clear a table without deallocating it + void clear() { + free_group(); + memset(bitmap, 0, sizeof(bitmap)); + settings.num_buckets = 0; + } + + // Functions that tell you about size. Alas, these aren't so useful + // because our table is always fixed size. + size_type size() const { return GROUP_SIZE; } + size_type max_size() const { return GROUP_SIZE; } + bool empty() const { return false; } + // We also may want to know how many *used* buckets there are + size_type num_nonempty() const { return settings.num_buckets; } + + + // get()/set() are explicitly const/non-const. You can use [] if + // you want something that can be either (potentially more expensive). + const_reference get(size_type i) const { + if ( bmtest(i) ) // bucket i is occupied + return group[pos_to_offset(bitmap, i)]; + else + return default_value(); // return the default reference + } + + // TODO(csilvers): make protected + friend + // This is used by sparse_hashtable to get an element from the table + // when we know it exists. + const_reference unsafe_get(size_type i) const { + assert(bmtest(i)); + return group[pos_to_offset(bitmap, i)]; + } + + // TODO(csilvers): make protected + friend + reference mutating_get(size_type i) { // fills bucket i before getting + if ( !bmtest(i) ) + set(i, default_value()); + return group[pos_to_offset(bitmap, i)]; + } + + // Syntactic sugar. It's easy to return a const reference. To + // return a non-const reference, we need to use the assigner adaptor. + const_reference operator[](size_type i) const { + return get(i); + } + + element_adaptor operator[](size_type i) { + return element_adaptor(this, i); + } + + private: + // Create space at group[offset], assuming value_type has trivial + // copy constructor and destructor, and the allocator_type is + // the default libc_allocator_with_alloc. (Really, we want it to have + // "trivial move", because that's what realloc and memmove both do. + // But there's no way to capture that using type_traits, so we + // pretend that move(x, y) is equivalent to "x.~T(); new(x) T(y);" + // which is pretty much correct, if a bit conservative.) + void set_aux(size_type offset, true_type) { + group = settings.realloc_or_die(group, settings.num_buckets+1); + // This is equivalent to memmove(), but faster on my Intel P4, + // at least with gcc4.1 -O2 / glibc 2.3.6. + for (size_type i = settings.num_buckets; i > offset; --i) + memcpy(group + i, group + i-1, sizeof(*group)); + } + + // Create space at group[offset], without special assumptions about value_type + // and allocator_type. + void set_aux(size_type offset, false_type) { + // This is valid because 0 <= offset <= num_buckets + pointer p = allocate_group(settings.num_buckets + 1); + uninitialized_copy(group, group + offset, p); + uninitialized_copy(group + offset, group + settings.num_buckets, + p + offset + 1); + free_group(); + group = p; + } + + public: + // This returns a reference to the inserted item (which is a copy of val). + // TODO(austern): Make this exception safe: handle exceptions from + // value_type's copy constructor. + reference set(size_type i, const_reference val) { + size_type offset = pos_to_offset(bitmap, i); // where we'll find (or insert) + if ( bmtest(i) ) { + // Delete the old value, which we're replacing with the new one + group[offset].~value_type(); + } else { + typedef integral_constant::value && + has_trivial_destructor::value && + is_same >::value)> + realloc_and_memmove_ok; // we pretend mv(x,y) == "x.~T(); new(x) T(y)" + set_aux(offset, realloc_and_memmove_ok()); + ++settings.num_buckets; + bmset(i); + } + // This does the actual inserting. Since we made the array using + // malloc, we use "placement new" to just call the constructor. + new(&group[offset]) value_type(val); + return group[offset]; + } + + // We let you see if a bucket is non-empty without retrieving it + bool test(size_type i) const { + return bmtest(i) != 0; + } + bool test(iterator pos) const { + return bmtest(pos.pos) != 0; + } + + private: + // Shrink the array, assuming value_type has trivial copy + // constructor and destructor, and the allocator_type is the default + // libc_allocator_with_alloc. (Really, we want it to have "trivial + // move", because that's what realloc and memmove both do. But + // there's no way to capture that using type_traits, so we pretend + // that move(x, y) is equivalent to ""x.~T(); new(x) T(y);" + // which is pretty much correct, if a bit conservative.) + void erase_aux(size_type offset, true_type) { + // This isn't technically necessary, since we know we have a + // trivial destructor, but is a cheap way to get a bit more safety. + group[offset].~value_type(); + // This is equivalent to memmove(), but faster on my Intel P4, + // at lesat with gcc4.1 -O2 / glibc 2.3.6. + assert(settings.num_buckets > 0); + for (size_type i = offset; i < settings.num_buckets-1; ++i) + memcpy(group + i, group + i+1, sizeof(*group)); // hopefully inlined! + group = settings.realloc_or_die(group, settings.num_buckets-1); + } + + // Shrink the array, without any special assumptions about value_type and + // allocator_type. + void erase_aux(size_type offset, false_type) { + // This is valid because 0 <= offset < num_buckets. Note the inequality. + pointer p = allocate_group(settings.num_buckets - 1); + uninitialized_copy(group, group + offset, p); + uninitialized_copy(group + offset + 1, group + settings.num_buckets, + p + offset); + free_group(); + group = p; + } + + public: + // This takes the specified elements out of the group. This is + // "undefining", rather than "clearing". + // TODO(austern): Make this exception safe: handle exceptions from + // value_type's copy constructor. + void erase(size_type i) { + if ( bmtest(i) ) { // trivial to erase empty bucket + size_type offset = pos_to_offset(bitmap,i); // where we'll find (or insert) + if ( settings.num_buckets == 1 ) { + free_group(); + group = NULL; + } else { + typedef integral_constant::value && + has_trivial_destructor::value && + is_same< + allocator_type, + libc_allocator_with_realloc >::value)> + realloc_and_memmove_ok; // pretend mv(x,y) == "x.~T(); new(x) T(y)" + erase_aux(offset, realloc_and_memmove_ok()); + } + --settings.num_buckets; + bmclear(i); + } + } + + void erase(iterator pos) { + erase(pos.pos); + } + + void erase(iterator start_it, iterator end_it) { + // This could be more efficient, but to do so we'd need to make + // bmclear() clear a range of indices. Doesn't seem worth it. + for ( ; start_it != end_it; ++start_it ) + erase(start_it); + } + + + // I/O + // We support reading and writing groups to disk. We don't store + // the actual array contents (which we don't know how to store), + // just the bitmap and size. Meant to be used with table I/O. + // Returns true if all was ok + bool write_metadata(FILE *fp) const { + // we explicitly set to u_int16_t + assert(sizeof(settings.num_buckets) == 2); + PUT_(settings.num_buckets, 8); + PUT_(settings.num_buckets, 0); + if ( !fwrite(bitmap, sizeof(bitmap), 1, fp) ) + return false; + return true; + } + + // Reading destroys the old group contents! Returns true if all was ok + bool read_metadata(FILE *fp) { + clear(); + + int x; // the GET_ macro requires an 'int x' to be defined + GET_(settings.num_buckets, 8); + GET_(settings.num_buckets, 0); + + if ( !fread(bitmap, sizeof(bitmap), 1, fp) ) return false; + + // We'll allocate the space, but we won't fill it: it will be + // left as uninitialized raw memory. + group = allocate_group(settings.num_buckets); + return true; + } + + // If your keys and values are simple enough, we can write them + // to disk for you. "simple enough" means POD and no pointers. + // However, we don't try to normalize endianness + bool write_nopointer_data(FILE *fp) const { + for ( const_nonempty_iterator it = nonempty_begin(); + it != nonempty_end(); ++it ) { + if ( !fwrite(&*it, sizeof(*it), 1, fp) ) return false; + } + return true; + } + + // When reading, we have to override the potential const-ness of *it. + // Again, only meaningful if value_type is a POD. + bool read_nopointer_data(FILE *fp) { + for ( nonempty_iterator it = nonempty_begin(); + it != nonempty_end(); ++it ) { + if ( !fread(reinterpret_cast(&(*it)), sizeof(*it), 1, fp) ) + return false; + } + return true; + } + + // Comparisons. Note the comparisons are pretty arbitrary: we + // compare values of the first index that isn't equal (using default + // value for empty buckets). + bool operator==(const sparsegroup& x) const { + return ( settings.num_buckets == x.settings.num_buckets && + memcmp(bitmap, x.bitmap, sizeof(bitmap)) == 0 && + STL_NAMESPACE::equal(begin(), end(), x.begin()) ); // from algorithm + } + bool operator<(const sparsegroup& x) const { // also from algorithm + return STL_NAMESPACE::lexicographical_compare(begin(), end(), + x.begin(), x.end()); + } + bool operator!=(const sparsegroup& x) const { return !(*this == x); } + bool operator<=(const sparsegroup& x) const { return !(x < *this); } + bool operator>(const sparsegroup& x) const { return x < *this; } + bool operator>=(const sparsegroup& x) const { return !(*this < x); } + + private: + template + class alloc_impl : public A { + public: + typedef typename A::pointer pointer; + typedef typename A::size_type size_type; + + // Convert a normal allocator to one that has realloc_or_die() + alloc_impl(const A& a) : A(a) { } + + // realloc_or_die should only be used when using the default + // allocator (libc_allocator_with_realloc). + pointer realloc_or_die(pointer /*ptr*/, size_type /*n*/) { + fprintf(stderr, "realloc_or_die is only supported for " + "libc_allocator_with_realloc"); + exit(1); + return NULL; + } + }; + + // A template specialization of alloc_impl for + // libc_allocator_with_realloc that can handle realloc_or_die. + template + class alloc_impl > + : public libc_allocator_with_realloc { + public: + typedef typename libc_allocator_with_realloc::pointer pointer; + typedef typename libc_allocator_with_realloc::size_type size_type; + + alloc_impl(const libc_allocator_with_realloc& a) + : libc_allocator_with_realloc(a) { } + + pointer realloc_or_die(pointer ptr, size_type n) { + pointer retval = this->reallocate(ptr, n); + if (retval == NULL) { + // We really should use PRIuS here, but I don't want to have to add + // a whole new configure option, with concomitant macro namespace + // pollution, just to print this (unlikely) error message. So I cast. + fprintf(stderr, "sparsehash: FATAL ERROR: failed to reallocate " + "%lu elements for ptr %p", + static_cast(n), ptr); + exit(1); + } + return retval; + } + }; + + // Package allocator with num_buckets to eliminate memory needed for the + // zero-size allocator. + // If new fields are added to this class, we should add them to + // operator= and swap. + class Settings : public alloc_impl { + public: + Settings(const alloc_impl& a, u_int16_t n = 0) + : alloc_impl(a), num_buckets(n) { } + Settings(const Settings& s) + : alloc_impl(s), num_buckets(s.num_buckets) { } + + u_int16_t num_buckets; // limits GROUP_SIZE to 64K + }; + + // The actual data + pointer group; // (small) array of T's + Settings settings; // allocator and num_buckets + unsigned char bitmap[(GROUP_SIZE-1)/8 + 1]; // fancy math is so we round up +}; + +// We need a global swap as well +template +inline void swap(sparsegroup &x, + sparsegroup &y) { + x.swap(y); +} + +// --------------------------------------------------------------------------- + + +template > +class sparsetable { + private: + typedef typename Alloc::template rebind::other value_alloc_type; + typedef typename Alloc::template rebind< + sparsegroup >::other vector_alloc; + + public: + // Basic types + typedef T value_type; // stolen from stl_vector.h + typedef Alloc allocator_type; + typedef typename value_alloc_type::size_type size_type; + typedef typename value_alloc_type::difference_type difference_type; + typedef typename value_alloc_type::reference reference; + typedef typename value_alloc_type::const_reference const_reference; + typedef typename value_alloc_type::pointer pointer; + typedef typename value_alloc_type::const_pointer const_pointer; + typedef table_iterator > iterator; + typedef const_table_iterator > + const_iterator; + typedef table_element_adaptor > + element_adaptor; + typedef STL_NAMESPACE::reverse_iterator const_reverse_iterator; + typedef STL_NAMESPACE::reverse_iterator reverse_iterator; + + // These are our special iterators, that go over non-empty buckets in a + // table. These aren't const only because you can change non-empty bcks. + typedef two_d_iterator< vector< sparsegroup, + vector_alloc> > + nonempty_iterator; + typedef const_two_d_iterator< vector< sparsegroup, + vector_alloc> > + const_nonempty_iterator; + typedef STL_NAMESPACE::reverse_iterator reverse_nonempty_iterator; + typedef STL_NAMESPACE::reverse_iterator const_reverse_nonempty_iterator; + // Another special iterator: it frees memory as it iterates (used to resize) + typedef destructive_two_d_iterator< vector< sparsegroup, + vector_alloc> > + destructive_iterator; + + // Iterator functions + iterator begin() { return iterator(this, 0); } + const_iterator begin() const { return const_iterator(this, 0); } + iterator end() { return iterator(this, size()); } + const_iterator end() const { return const_iterator(this, size()); } + reverse_iterator rbegin() { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } + reverse_iterator rend() { return reverse_iterator(begin()); } + const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } + + // Versions for our special non-empty iterator + nonempty_iterator nonempty_begin() { + return nonempty_iterator(groups.begin(), groups.end(), groups.begin()); + } + const_nonempty_iterator nonempty_begin() const { + return const_nonempty_iterator(groups.begin(),groups.end(), groups.begin()); + } + nonempty_iterator nonempty_end() { + return nonempty_iterator(groups.begin(), groups.end(), groups.end()); + } + const_nonempty_iterator nonempty_end() const { + return const_nonempty_iterator(groups.begin(), groups.end(), groups.end()); + } + reverse_nonempty_iterator nonempty_rbegin() { + return reverse_nonempty_iterator(nonempty_end()); + } + const_reverse_nonempty_iterator nonempty_rbegin() const { + return const_reverse_nonempty_iterator(nonempty_end()); + } + reverse_nonempty_iterator nonempty_rend() { + return reverse_nonempty_iterator(nonempty_begin()); + } + const_reverse_nonempty_iterator nonempty_rend() const { + return const_reverse_nonempty_iterator(nonempty_begin()); + } + destructive_iterator destructive_begin() { + return destructive_iterator(groups.begin(), groups.end(), groups.begin()); + } + destructive_iterator destructive_end() { + return destructive_iterator(groups.begin(), groups.end(), groups.end()); + } + + typedef sparsegroup group_type; + typedef vector group_vector_type; + + typedef typename group_vector_type::reference GroupsReference; + typedef typename group_vector_type::const_reference GroupsConstReference; + typedef typename group_vector_type::iterator GroupsIterator; + typedef typename group_vector_type::const_iterator GroupsConstIterator; + + // How to deal with the proper group + static size_type num_groups(size_type num) { // how many to hold num buckets + return num == 0 ? 0 : ((num-1) / GROUP_SIZE) + 1; + } + + u_int16_t pos_in_group(size_type i) const { + return static_cast(i % GROUP_SIZE); + } + size_type group_num(size_type i) const { + return i / GROUP_SIZE; + } + GroupsReference which_group(size_type i) { + return groups[group_num(i)]; + } + GroupsConstReference which_group(size_type i) const { + return groups[group_num(i)]; + } + + public: + // Constructors -- default, normal (when you specify size), and copy + sparsetable(size_type sz = 0, Alloc alloc = Alloc()) + : groups(vector_alloc(alloc)), settings(alloc, sz) { + groups.resize(num_groups(sz), group_type(settings)); + } + // We can get away with using the default copy constructor, + // and default destructor, and hence the default operator=. Huzzah! + + // Many STL algorithms use swap instead of copy constructors + void swap(sparsetable& x) { + STL_NAMESPACE::swap(groups, x.groups); + STL_NAMESPACE::swap(settings.table_size, x.settings.table_size); + STL_NAMESPACE::swap(settings.num_buckets, x.settings.num_buckets); + } + + // It's always nice to be able to clear a table without deallocating it + void clear() { + GroupsIterator group; + for ( group = groups.begin(); group != groups.end(); ++group ) { + group->clear(); + } + settings.num_buckets = 0; + } + + // ACCESSOR FUNCTIONS for the things we templatize on, basically + allocator_type get_allocator() const { + return allocator_type(settings); + } + + + // Functions that tell you about size. + // NOTE: empty() is non-intuitive! It does not tell you the number + // of not-empty buckets (use num_nonempty() for that). Instead + // it says whether you've allocated any buckets or not. + size_type size() const { return settings.table_size; } + size_type max_size() const { return settings.max_size(); } + bool empty() const { return settings.table_size == 0; } + // We also may want to know how many *used* buckets there are + size_type num_nonempty() const { return settings.num_buckets; } + + // OK, we'll let you resize one of these puppies + void resize(size_type new_size) { + groups.resize(num_groups(new_size), group_type(settings)); + if ( new_size < settings.table_size) { + // lower num_buckets, clear last group + if ( pos_in_group(new_size) > 0 ) // need to clear inside last group + groups.back().erase(groups.back().begin() + pos_in_group(new_size), + groups.back().end()); + settings.num_buckets = 0; // refigure # of used buckets + GroupsConstIterator group; + for ( group = groups.begin(); group != groups.end(); ++group ) + settings.num_buckets += group->num_nonempty(); + } + settings.table_size = new_size; + } + + + // We let you see if a bucket is non-empty without retrieving it + bool test(size_type i) const { + return which_group(i).test(pos_in_group(i)); + } + bool test(iterator pos) const { + return which_group(pos.pos).test(pos_in_group(pos.pos)); + } + bool test(const_iterator pos) const { + return which_group(pos.pos).test(pos_in_group(pos.pos)); + } + + // We only return const_references because it's really hard to + // return something settable for empty buckets. Use set() instead. + const_reference get(size_type i) const { + assert(i < settings.table_size); + return which_group(i).get(pos_in_group(i)); + } + + // TODO(csilvers): make protected + friend + // This is used by sparse_hashtable to get an element from the table + // when we know it exists (because the caller has called test(i)). + const_reference unsafe_get(size_type i) const { + assert(i < settings.table_size); + assert(test(i)); + return which_group(i).unsafe_get(pos_in_group(i)); + } + + // TODO(csilvers): make protected + friend element_adaptor + reference mutating_get(size_type i) { // fills bucket i before getting + assert(i < settings.table_size); + size_type old_numbuckets = which_group(i).num_nonempty(); + reference retval = which_group(i).mutating_get(pos_in_group(i)); + settings.num_buckets += which_group(i).num_nonempty() - old_numbuckets; + return retval; + } + + // Syntactic sugar. As in sparsegroup, the non-const version is harder + const_reference operator[](size_type i) const { + return get(i); + } + + element_adaptor operator[](size_type i) { + return element_adaptor(this, i); + } + + // Needed for hashtables, gets as a nonempty_iterator. Crashes for empty bcks + const_nonempty_iterator get_iter(size_type i) const { + assert(test(i)); // how can a nonempty_iterator point to an empty bucket? + return const_nonempty_iterator( + groups.begin(), groups.end(), + groups.begin() + group_num(i), + (groups[group_num(i)].nonempty_begin() + + groups[group_num(i)].pos_to_offset(pos_in_group(i)))); + } + // For nonempty we can return a non-const version + nonempty_iterator get_iter(size_type i) { + assert(test(i)); // how can a nonempty_iterator point to an empty bucket? + return nonempty_iterator( + groups.begin(), groups.end(), + groups.begin() + group_num(i), + (groups[group_num(i)].nonempty_begin() + + groups[group_num(i)].pos_to_offset(pos_in_group(i)))); + } + + + // This returns a reference to the inserted item (which is a copy of val) + // The trick is to figure out whether we're replacing or inserting anew + reference set(size_type i, const_reference val) { + assert(i < settings.table_size); + size_type old_numbuckets = which_group(i).num_nonempty(); + reference retval = which_group(i).set(pos_in_group(i), val); + settings.num_buckets += which_group(i).num_nonempty() - old_numbuckets; + return retval; + } + + // This takes the specified elements out of the table. This is + // "undefining", rather than "clearing". + void erase(size_type i) { + assert(i < settings.table_size); + size_type old_numbuckets = which_group(i).num_nonempty(); + which_group(i).erase(pos_in_group(i)); + settings.num_buckets += which_group(i).num_nonempty() - old_numbuckets; + } + + void erase(iterator pos) { + erase(pos.pos); + } + + void erase(iterator start_it, iterator end_it) { + // This could be more efficient, but then we'd need to figure + // out if we spanned groups or not. Doesn't seem worth it. + for ( ; start_it != end_it; ++start_it ) + erase(start_it); + } + + + // We support reading and writing tables to disk. We don't store + // the actual array contents (which we don't know how to store), + // just the groups and sizes. Returns true if all went ok. + + private: + // Every time the disk format changes, this should probably change too + static const unsigned long MAGIC_NUMBER = 0x24687531; + + // Old versions of this code write all data in 32 bits. We need to + // support these files as well as having support for 64-bit systems. + // So we use the following encoding scheme: for values < 2^32-1, we + // store in 4 bytes in big-endian order. For values > 2^32, we + // store 0xFFFFFFF followed by 8 bytes in big-endian order. This + // causes us to mis-read old-version code that stores exactly + // 0xFFFFFFF, but I don't think that is likely to have happened for + // these particular values. + static bool write_32_or_64(FILE* fp, size_type value) { + if ( value < 0xFFFFFFFFULL ) { // fits in 4 bytes + PUT_(value, 24); + PUT_(value, 16); + PUT_(value, 8); + PUT_(value, 0); + } else if ( value == 0xFFFFFFFFUL ) { // special case in 32bit systems + PUT_(0xFF, 0); PUT_(0xFF, 0); PUT_(0xFF, 0); PUT_(0xFF, 0); // marker + PUT_(0, 0); PUT_(0, 0); PUT_(0, 0); PUT_(0, 0); + PUT_(0xFF, 0); PUT_(0xFF, 0); PUT_(0xFF, 0); PUT_(0xFF, 0); + } else { + PUT_(0xFF, 0); PUT_(0xFF, 0); PUT_(0xFF, 0); PUT_(0xFF, 0); // marker + PUT_(value, 56); + PUT_(value, 48); + PUT_(value, 40); + PUT_(value, 32); + PUT_(value, 24); + PUT_(value, 16); + PUT_(value, 8); + PUT_(value, 0); + } + return true; + } + + static bool read_32_or_64(FILE* fp, size_type *value) { // reads into value + size_type first4 = 0; + int x; + GET_(first4, 24); + GET_(first4, 16); + GET_(first4, 8); + GET_(first4, 0); + if ( first4 < 0xFFFFFFFFULL ) { + *value = first4; + } else { + GET_(*value, 56); + GET_(*value, 48); + GET_(*value, 40); + GET_(*value, 32); + GET_(*value, 24); + GET_(*value, 16); + GET_(*value, 8); + GET_(*value, 0); + } + return true; + } + + public: + bool write_metadata(FILE *fp) const { + if ( !write_32_or_64(fp, MAGIC_NUMBER) ) return false; + if ( !write_32_or_64(fp, settings.table_size) ) return false; + if ( !write_32_or_64(fp, settings.num_buckets) ) return false; + + GroupsConstIterator group; + for ( group = groups.begin(); group != groups.end(); ++group ) + if ( group->write_metadata(fp) == false ) return false; + return true; + } + + // Reading destroys the old table contents! Returns true if read ok. + bool read_metadata(FILE *fp) { + size_type magic_read = 0; + if ( !read_32_or_64(fp, &magic_read) ) return false; + if ( magic_read != MAGIC_NUMBER ) { + clear(); // just to be consistent + return false; + } + + if ( !read_32_or_64(fp, &settings.table_size) ) return false; + if ( !read_32_or_64(fp, &settings.num_buckets) ) return false; + + resize(settings.table_size); // so the vector's sized ok + GroupsIterator group; + for ( group = groups.begin(); group != groups.end(); ++group ) + if ( group->read_metadata(fp) == false ) return false; + return true; + } + + // This code is identical to that for SparseGroup + // If your keys and values are simple enough, we can write them + // to disk for you. "simple enough" means no pointers. + // However, we don't try to normalize endianness + bool write_nopointer_data(FILE *fp) const { + for ( const_nonempty_iterator it = nonempty_begin(); + it != nonempty_end(); ++it ) { + if ( !fwrite(&*it, sizeof(*it), 1, fp) ) return false; + } + return true; + } + + // When reading, we have to override the potential const-ness of *it + bool read_nopointer_data(FILE *fp) { + for ( nonempty_iterator it = nonempty_begin(); + it != nonempty_end(); ++it ) { + if ( !fread(reinterpret_cast(&(*it)), sizeof(*it), 1, fp) ) + return false; + } + return true; + } + + // Comparisons. Note the comparisons are pretty arbitrary: we + // compare values of the first index that isn't equal (using default + // value for empty buckets). + bool operator==(const sparsetable& x) const { + return ( settings.table_size == x.settings.table_size && + settings.num_buckets == x.settings.num_buckets && + groups == x.groups ); + } + bool operator<(const sparsetable& x) const { // also from algobase.h + return STL_NAMESPACE::lexicographical_compare(begin(), end(), + x.begin(), x.end()); + } + bool operator!=(const sparsetable& x) const { return !(*this == x); } + bool operator<=(const sparsetable& x) const { return !(x < *this); } + bool operator>(const sparsetable& x) const { return x < *this; } + bool operator>=(const sparsetable& x) const { return !(*this < x); } + + + private: + // Package allocator with table_size and num_buckets to eliminate memory + // needed for the zero-size allocator. + // If new fields are added to this class, we should add them to + // operator= and swap. + class Settings : public allocator_type { + public: + typedef typename allocator_type::size_type size_type; + + Settings(const allocator_type& a, size_type sz = 0, size_type n = 0) + : allocator_type(a), table_size(sz), num_buckets(n) { } + + Settings(const Settings& s) + : allocator_type(s), + table_size(s.table_size), num_buckets(s.num_buckets) { } + + size_type table_size; // how many buckets they want + size_type num_buckets; // number of non-empty buckets + }; + + // The actual data + group_vector_type groups; // our list of groups + Settings settings; // allocator, table size, buckets +}; + +// We need a global swap as well +template +inline void swap(sparsetable &x, + sparsetable &y) { + x.swap(y); +} + +#undef GET_ +#undef PUT_ + +_END_GOOGLE_NAMESPACE_ + +#endif diff --git a/external/google/type_traits.h b/external/google/type_traits.h new file mode 100644 index 00000000..87729d40 --- /dev/null +++ b/external/google/type_traits.h @@ -0,0 +1,336 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// ---- +// Author: Matt Austern +// +// Define a small subset of tr1 type traits. The traits we define are: +// is_integral +// is_floating_point +// is_pointer +// is_enum +// is_reference +// is_pod +// has_trivial_constructor +// has_trivial_copy +// has_trivial_assign +// has_trivial_destructor +// remove_const +// remove_volatile +// remove_cv +// remove_reference +// add_reference +// remove_pointer +// is_same +// is_convertible +// We can add more type traits as required. + +#ifndef BASE_TYPE_TRAITS_H_ +#define BASE_TYPE_TRAITS_H_ + +#include +#include // For pair + +_START_GOOGLE_NAMESPACE_ + +// integral_constant, defined in tr1, is a wrapper for an integer +// value. We don't really need this generality; we could get away +// with hardcoding the integer type to bool. We use the fully +// general integer_constant for compatibility with tr1. + +template +struct integral_constant { + static const T value = v; + typedef T value_type; + typedef integral_constant type; +}; + +template const T integral_constant::value; + +// Abbreviations: true_type and false_type are structs that represent +// boolean true and false values. +typedef integral_constant true_type; +typedef integral_constant false_type; + +// Types small_ and big_ are guaranteed such that sizeof(small_) < +// sizeof(big_) +typedef char small_; + +struct big_ { + char dummy[2]; +}; + +template struct is_integral; +template struct is_floating_point; +template struct is_pointer; +// MSVC can't compile this correctly, and neither can gcc 3.3.5 (at least) +#if !defined(_MSC_VER) && !(defined(__GNUC__) && __GNUC__ <= 3) +// is_enum uses is_convertible, which is not available on MSVC. +template struct is_enum; +#endif +template struct is_reference; +template struct is_pod; +template struct has_trivial_constructor; +template struct has_trivial_copy; +template struct has_trivial_assign; +template struct has_trivial_destructor; +template struct remove_const; +template struct remove_volatile; +template struct remove_cv; +template struct remove_reference; +template struct add_reference; +template struct remove_pointer; +template struct is_same; +#if !defined(_MSC_VER) && !(defined(__GNUC__) && __GNUC__ <= 3) +template struct is_convertible; +#endif + +// is_integral is false except for the built-in integer types. +template struct is_integral : false_type { }; +template<> struct is_integral : true_type { }; +template<> struct is_integral : true_type { }; +template<> struct is_integral : true_type { }; +template<> struct is_integral : true_type { }; +#if defined(_MSC_VER) +// wchar_t is not by default a distinct type from unsigned short in +// Microsoft C. +// See http://msdn2.microsoft.com/en-us/library/dh8che7s(VS.80).aspx +template<> struct is_integral<__wchar_t> : true_type { }; +#else +template<> struct is_integral : true_type { }; +#endif +template<> struct is_integral : true_type { }; +template<> struct is_integral : true_type { }; +template<> struct is_integral : true_type { }; +template<> struct is_integral : true_type { }; +template<> struct is_integral : true_type { }; +template<> struct is_integral : true_type { }; +#ifdef HAVE_LONG_LONG +template<> struct is_integral : true_type { }; +template<> struct is_integral : true_type { }; +#endif + + +// is_floating_point is false except for the built-in floating-point types. +template struct is_floating_point : false_type { }; +template<> struct is_floating_point : true_type { }; +template<> struct is_floating_point : true_type { }; +template<> struct is_floating_point : true_type { }; + + +// is_pointer is false except for pointer types. +template struct is_pointer : false_type { }; +template struct is_pointer : true_type { }; + +#if !defined(_MSC_VER) && !(defined(__GNUC__) && __GNUC__ <= 3) + +namespace internal { + +template struct is_class_or_union { + template static small_ tester(void (U::*)()); + template static big_ tester(...); + static const bool value = sizeof(tester(0)) == sizeof(small_); +}; + +// is_convertible chokes if the first argument is an array. That's why +// we use add_reference here. +template struct is_enum_impl + : is_convertible::type, int> { }; + +template struct is_enum_impl : false_type { }; + +} // namespace internal + +// Specified by TR1 [4.5.1] primary type categories. + +// Implementation note: +// +// Each type is either void, integral, floating point, array, pointer, +// reference, member object pointer, member function pointer, enum, +// union or class. Out of these, only integral, floating point, reference, +// class and enum types are potentially convertible to int. Therefore, +// if a type is not a reference, integral, floating point or class and +// is convertible to int, it's a enum. +// +// Is-convertible-to-int check is done only if all other checks pass, +// because it can't be used with some types (e.g. void or classes with +// inaccessible conversion operators). +template struct is_enum + : internal::is_enum_impl< + is_same::value || + is_integral::value || + is_floating_point::value || + is_reference::value || + internal::is_class_or_union::value, + T> { }; + +template struct is_enum : is_enum { }; +template struct is_enum : is_enum { }; +template struct is_enum : is_enum { }; + +#endif + +// is_reference is false except for reference types. +template struct is_reference : false_type {}; +template struct is_reference : true_type {}; + + +// We can't get is_pod right without compiler help, so fail conservatively. +// We will assume it's false except for arithmetic types, enumerations, +// pointers and const versions thereof. Note that std::pair is not a POD. +template struct is_pod + : integral_constant::value || + is_floating_point::value || +#if !defined(_MSC_VER) && !(defined(__GNUC__) && __GNUC__ <= 3) + // is_enum is not available on MSVC. + is_enum::value || +#endif + is_pointer::value)> { }; +template struct is_pod : is_pod { }; + + +// We can't get has_trivial_constructor right without compiler help, so +// fail conservatively. We will assume it's false except for: (1) types +// for which is_pod is true. (2) std::pair of types with trivial +// constructors. (3) array of a type with a trivial constructor. +// (4) const versions thereof. +template struct has_trivial_constructor : is_pod { }; +template struct has_trivial_constructor > + : integral_constant::value && + has_trivial_constructor::value)> { }; +template struct has_trivial_constructor + : has_trivial_constructor { }; +template struct has_trivial_constructor + : has_trivial_constructor { }; + +// We can't get has_trivial_copy right without compiler help, so fail +// conservatively. We will assume it's false except for: (1) types +// for which is_pod is true. (2) std::pair of types with trivial copy +// constructors. (3) array of a type with a trivial copy constructor. +// (4) const versions thereof. +template struct has_trivial_copy : is_pod { }; +template struct has_trivial_copy > + : integral_constant::value && + has_trivial_copy::value)> { }; +template struct has_trivial_copy + : has_trivial_copy { }; +template struct has_trivial_copy : has_trivial_copy { }; + +// We can't get has_trivial_assign right without compiler help, so fail +// conservatively. We will assume it's false except for: (1) types +// for which is_pod is true. (2) std::pair of types with trivial copy +// constructors. (3) array of a type with a trivial assign constructor. +template struct has_trivial_assign : is_pod { }; +template struct has_trivial_assign > + : integral_constant::value && + has_trivial_assign::value)> { }; +template struct has_trivial_assign + : has_trivial_assign { }; + +// We can't get has_trivial_destructor right without compiler help, so +// fail conservatively. We will assume it's false except for: (1) types +// for which is_pod is true. (2) std::pair of types with trivial +// destructors. (3) array of a type with a trivial destructor. +// (4) const versions thereof. +template struct has_trivial_destructor : is_pod { }; +template struct has_trivial_destructor > + : integral_constant::value && + has_trivial_destructor::value)> { }; +template struct has_trivial_destructor + : has_trivial_destructor { }; +template struct has_trivial_destructor + : has_trivial_destructor { }; + +// Specified by TR1 [4.7.1] +template struct remove_const { typedef T type; }; +template struct remove_const { typedef T type; }; +template struct remove_volatile { typedef T type; }; +template struct remove_volatile { typedef T type; }; +template struct remove_cv { + typedef typename remove_const::type>::type type; +}; + + +// Specified by TR1 [4.7.2] Reference modifications. +template struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; + +template struct add_reference { typedef T& type; }; +template struct add_reference { typedef T& type; }; + +// Specified by TR1 [4.7.4] Pointer modifications. +template struct remove_pointer { typedef T type; }; +template struct remove_pointer { typedef T type; }; +template struct remove_pointer { typedef T type; }; +template struct remove_pointer { typedef T type; }; +template struct remove_pointer { + typedef T type; }; + +// Specified by TR1 [4.6] Relationships between types +template struct is_same : public false_type { }; +template struct is_same : public true_type { }; + +// Specified by TR1 [4.6] Relationships between types +#if !defined(_MSC_VER) && !(defined(__GNUC__) && __GNUC__ <= 3) +namespace internal { + +// This class is an implementation detail for is_convertible, and you +// don't need to know how it works to use is_convertible. For those +// who care: we declare two different functions, one whose argument is +// of type To and one with a variadic argument list. We give them +// return types of different size, so we can use sizeof to trick the +// compiler into telling us which function it would have chosen if we +// had called it with an argument of type From. See Alexandrescu's +// _Modern C++ Design_ for more details on this sort of trick. + +template +struct ConvertHelper { + static small_ Test(To); + static big_ Test(...); + static From Create(); +}; +} // namespace internal + +// Inherits from true_type if From is convertible to To, false_type otherwise. +template +struct is_convertible + : integral_constant::Test( + internal::ConvertHelper::Create())) + == sizeof(small_)> { +}; +#endif + +_END_GOOGLE_NAMESPACE_ + +#endif // BASE_TYPE_TRAITS_H_ diff --git a/include/INode.h b/include/INode.h index 96026119..f6a013a7 100644 --- a/include/INode.h +++ b/include/INode.h @@ -62,7 +62,7 @@ public: virtual uint64_t getLastLocalBlockHeight() const = 0; virtual uint64_t getLastKnownBlockHeight() const = 0; - virtual void relayTransaction(const cryptonote::transaction& transaction, const Callback& callback) = 0; + virtual void relayTransaction(const cryptonote::Transaction& transaction, const Callback& callback) = 0; virtual void getRandomOutsByAmounts(std::vector&& amounts, uint64_t outsCount, std::vector& result, const Callback& callback) = 0; virtual void getNewBlocks(std::list&& knownBlockIds, std::list& newBlocks, uint64_t& startHeight, const Callback& callback) = 0; virtual void getTransactionOutsGlobalIndices(const crypto::hash& transactionHash, std::vector& outsGlobalIndices, const Callback& callback) = 0; diff --git a/include/IWallet.h b/include/IWallet.h index 8d6c5308..26a2dcab 100644 --- a/include/IWallet.h +++ b/include/IWallet.h @@ -41,7 +41,7 @@ const TransactionId INVALID_TRANSACTION_ID = std::numeric_limits::max(); const uint64_t UNCONFIRMED_TRANSACTION_HEIGHT = std::numeric_limits::max(); -struct Transaction { +struct TransactionInfo { TransferId firstTransferId; size_t transferCount; int64_t totalAmount; @@ -89,7 +89,7 @@ public: virtual TransactionId findTransactionByTransferId(TransferId transferId) = 0; - virtual bool getTransaction(TransactionId transactionId, Transaction& transaction) = 0; + virtual bool getTransaction(TransactionId transactionId, TransactionInfo& transaction) = 0; virtual bool getTransfer(TransferId transferId, Transfer& transfer) = 0; virtual TransactionId sendTransaction(const Transfer& transfer, uint64_t fee, const std::string& extra = "", uint64_t mixIn = 0, uint64_t unlockTimestamp = 0) = 0; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0a446320..9f18c3d5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,7 +2,7 @@ add_definitions(-DSTATICLIB) file(GLOB_RECURSE COMMON common/*) file(GLOB_RECURSE CRYPTO crypto/*) -file(GLOB_RECURSE CRYPTONOTE_CORE cryptonote_core/*) +file(GLOB_RECURSE CRYPTONOTE_CORE cryptonote_core/* cryptonote_config.h) file(GLOB_RECURSE CRYPTONOTE_PROTOCOL cryptonote_protocol/*) file(GLOB_RECURSE DAEMON daemon/*) file(GLOB_RECURSE P2P p2p/*) @@ -32,15 +32,16 @@ add_library(cryptonote_core ${CRYPTONOTE_CORE}) add_executable(daemon ${DAEMON} ${P2P} ${CRYPTONOTE_PROTOCOL}) add_executable(connectivity_tool ${CONN_TOOL}) add_executable(simpleminer ${MINER}) -target_link_libraries(daemon rpc cryptonote_core crypto common upnpc-static ${Boost_LIBRARIES}) -target_link_libraries(connectivity_tool cryptonote_core crypto common ${Boost_LIBRARIES}) -target_link_libraries(simpleminer cryptonote_core crypto common ${Boost_LIBRARIES}) +target_link_libraries(daemon epee rpc cryptonote_core crypto common upnpc-static ${Boost_LIBRARIES}) +target_link_libraries(connectivity_tool epee cryptonote_core crypto common ${Boost_LIBRARIES}) +target_link_libraries(simpleminer epee cryptonote_core crypto common ${Boost_LIBRARIES}) add_library(rpc ${RPC}) add_library(wallet ${WALLET}) add_executable(simplewallet ${SIMPLEWALLET} ) -target_link_libraries(simplewallet wallet rpc cryptonote_core crypto common upnpc-static ${Boost_LIBRARIES}) +target_link_libraries(simplewallet epee wallet rpc cryptonote_core crypto common upnpc-static ${Boost_LIBRARIES}) add_library(node_rpc_proxy ${NODE_RPC_PROXY}) +add_dependencies(connectivity_tool version) add_dependencies(daemon version) add_dependencies(rpc version) add_dependencies(simplewallet version) diff --git a/src/common/BlockingQueue.cpp b/src/common/BlockingQueue.cpp new file mode 100644 index 00000000..3778bec4 --- /dev/null +++ b/src/common/BlockingQueue.cpp @@ -0,0 +1,18 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "BlockingQueue.h" diff --git a/src/common/BlockingQueue.h b/src/common/BlockingQueue.h new file mode 100644 index 00000000..bdae78ca --- /dev/null +++ b/src/common/BlockingQueue.h @@ -0,0 +1,126 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include +#include +#include +#include +#include + +template < typename T, typename Container = std::deque > +class BlockingQueue { +public: + + typedef BlockingQueue ThisType; + + BlockingQueue(size_t maxSize = 1) : + m_maxSize(maxSize), m_closed(false) {} + + template + bool push(TT&& v) { + std::unique_lock lk(m_mutex); + + while (!m_closed && m_queue.size() >= m_maxSize) { + m_haveSpace.wait(lk); + } + + if (m_closed) { + return false; + } + + m_queue.push_back(std::forward(v)); + m_haveData.notify_one(); + return true; + } + + bool pop(T& v) { + std::unique_lock lk(m_mutex); + + while (m_queue.empty()) { + if (m_closed) { + // all data has been processed, queue is closed + return false; + } + m_haveData.wait(lk); + } + + v = std::move(m_queue.front()); + m_queue.pop_front(); + + // we can have several waiting threads to unblock + if (m_closed && m_queue.empty()) + m_haveSpace.notify_all(); + else + m_haveSpace.notify_one(); + + return true; + } + + void close(bool wait = false) { + std::unique_lock lk(m_mutex); + m_closed = true; + m_haveData.notify_all(); // wake up threads in pop() + + if (wait) { + while (!m_queue.empty()) { + m_haveSpace.wait(lk); + } + } + } + + size_t size() { + std::unique_lock lk(m_mutex); + return m_queue.size(); + } + + size_t capacity() const { + return m_maxSize; + } + +private: + + const size_t m_maxSize; + Container m_queue; + bool m_closed; + + std::mutex m_mutex; + std::condition_variable m_haveData; + std::condition_variable m_haveSpace; +}; + +template +class GroupClose { +public: + + GroupClose(QueueT& queue, size_t groupSize) + : m_queue(queue), m_count(groupSize) {} + + void close() { + if (m_count == 0) + return; + if (m_count.fetch_sub(1) == 1) + m_queue.close(); + } + +private: + + std::atomic m_count; + QueueT& m_queue; + +}; diff --git a/src/common/ObserverManager.h b/src/common/ObserverManager.h index 772f8ae2..4387b21c 100644 --- a/src/common/ObserverManager.h +++ b/src/common/ObserverManager.h @@ -17,6 +17,7 @@ #pragma once +#include #include #include diff --git a/src/common/SignalHandler.cpp b/src/common/SignalHandler.cpp new file mode 100644 index 00000000..da63a537 --- /dev/null +++ b/src/common/SignalHandler.cpp @@ -0,0 +1,54 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "SignalHandler.h" + +#include +#include + +// epee +#include "include_base_utils.h" + + +namespace tools { + std::function SignalHandler::m_handler; + +#if defined(WIN32) + BOOL WINAPI SignalHandler::winHandler(DWORD type) { + if (CTRL_C_EVENT == type || CTRL_BREAK_EVENT == type) { + handleSignal(); + return TRUE; + } else { + LOG_PRINT_RED_L0("Got control signal " << type << ". Exiting without saving..."); + return FALSE; + } + return TRUE; + } + +#else + + void SignalHandler::posixHandler(int /*type*/) { + handleSignal(); + } +#endif + + void SignalHandler::handleSignal() { + static std::mutex m_mutex; + std::unique_lock lock(m_mutex); + m_handler(); + } +} diff --git a/src/common/SignalHandler.h b/src/common/SignalHandler.h new file mode 100644 index 00000000..bfab0b89 --- /dev/null +++ b/src/common/SignalHandler.h @@ -0,0 +1,59 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include +#include + +#include "misc_os_dependent.h" + +namespace tools { + class SignalHandler + { + public: + template + static bool install(T t) + { +#if defined(WIN32) + bool r = TRUE == ::SetConsoleCtrlHandler(&winHandler, TRUE); + if (r) + { + m_handler = t; + } + return r; +#else + signal(SIGINT, posixHandler); + signal(SIGTERM, posixHandler); + m_handler = t; + return true; +#endif + } + + private: +#if defined(WIN32) + static BOOL WINAPI winHandler(DWORD type); +#else + static void posixHandler(int /*type*/); +#endif + + static void handleSignal(); + + private: + static std::function m_handler; + }; +} diff --git a/src/common/base58.cpp b/src/common/base58.cpp index ea71004a..926a56b4 100644 --- a/src/common/base58.cpp +++ b/src/common/base58.cpp @@ -23,7 +23,6 @@ #include "crypto/hash.h" #include "int-util.h" -#include "util.h" #include "varint.h" namespace tools diff --git a/src/common/boost_serialization_helper.h b/src/common/boost_serialization_helper.h index 5d08ee4b..7bfac1f8 100644 --- a/src/common/boost_serialization_helper.h +++ b/src/common/boost_serialization_helper.h @@ -17,9 +17,18 @@ #pragma once +#if defined(WIN32) +#include +#endif + +#include + #include #include +// epee +#include "include_base_utils.h" +#include "misc_os_dependent.h" namespace tools { diff --git a/src/common/unordered_containers_boost_serialization.h b/src/common/unordered_containers_boost_serialization.h index 20a36fd6..f8409c5e 100644 --- a/src/common/unordered_containers_boost_serialization.h +++ b/src/common/unordered_containers_boost_serialization.h @@ -21,6 +21,9 @@ #include #include +#include "google/sparse_hash_set" +#include "google/sparse_hash_map" + namespace boost { namespace serialization @@ -108,6 +111,70 @@ namespace boost } } + template + inline void save(Archive &a, const ::google::sparse_hash_set &x, const boost::serialization::version_type ver) + { + size_t s = x.size(); + a << s; + BOOST_FOREACH(auto& v, x) + { + a << v; + } + } + + template + inline void load(Archive &a, ::google::sparse_hash_set &x, const boost::serialization::version_type ver) + { + x.clear(); + size_t s = 0; + a >> s; + for(size_t i = 0; i != s; i++) + { + hval v; + a >> v; + x.insert(v); + } + } + + template + inline void serialize(Archive &a, ::google::sparse_hash_set &x, const boost::serialization::version_type ver) + { + split_free(a, x, ver); + } + + template + inline void save(Archive &a, const ::google::sparse_hash_map &x, const boost::serialization::version_type ver) + { + size_t s = x.size(); + a << s; + BOOST_FOREACH(auto& v, x) + { + a << v.first; + a << v.second; + } + } + + template + inline void load(Archive &a, ::google::sparse_hash_map &x, const boost::serialization::version_type ver) + { + x.clear(); + size_t s = 0; + a >> s; + for(size_t i = 0; i != s; i++) + { + h_key k; + hval v; + a >> k; + a >> v; + x.insert(std::pair(k, v)); + } + } + + template + inline void serialize(Archive &a, ::google::sparse_hash_map &x, const boost::serialization::version_type ver) + { + split_free(a, x, ver); + } template inline void serialize(Archive &a, std::unordered_map &x, const boost::serialization::version_type ver) diff --git a/src/common/util.cpp b/src/common/util.cpp index 98f0e807..16786d58 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -15,12 +15,16 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . +#include "util.h" + #include +#include + #include "include_base_utils.h" using namespace epee; -#include "util.h" +#include "p2p/p2p_protocol_defs.h" #include "cryptonote_config.h" #ifdef WIN32 @@ -34,8 +38,6 @@ using namespace epee; namespace tools { - std::function signal_handler::m_handler; - #ifdef WIN32 std::string get_windows_version_display_string() { @@ -311,7 +313,7 @@ std::string get_nix_version_display_string() std::string config_folder; #ifdef WIN32 // Windows - config_folder = get_special_folder_path(CSIDL_APPDATA, true) + "/" + CRYPTONOTE_NAME; + config_folder = get_special_folder_path(CSIDL_APPDATA, true) + "/" + cryptonote::CRYPTONOTE_NAME; #else std::string pathRet; char* pszHome = getenv("HOME"); @@ -322,10 +324,10 @@ std::string get_nix_version_display_string() #ifdef MAC_OSX // Mac pathRet /= "Library/Application Support"; - config_folder = (pathRet + "/" + CRYPTONOTE_NAME); + config_folder = (pathRet + "/" + cryptonote::CRYPTONOTE_NAME); #else // Unix - config_folder = (pathRet + "/." + CRYPTONOTE_NAME); + config_folder = (pathRet + "/." + cryptonote::CRYPTONOTE_NAME); #endif #endif @@ -374,4 +376,12 @@ std::string get_nix_version_display_string() #endif return std::error_code(code, std::system_category()); } + + crypto::hash get_proof_of_trust_hash(const nodetool::proof_of_trust& pot) + { + std::string s; + s.append(reinterpret_cast(&pot.peer_id), sizeof(pot.peer_id)); + s.append(reinterpret_cast(&pot.time), sizeof(pot.time)); + return crypto::cn_fast_hash(s.data(), s.size()); + } } diff --git a/src/common/util.h b/src/common/util.h index cac14bc2..514bd90f 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -17,14 +17,15 @@ #pragma once -#include +#include #include -#include -#include "crypto/crypto.h" #include "crypto/hash.h" -#include "misc_language.h" -#include "p2p/p2p_protocol_defs.h" + + +namespace nodetool { + struct proof_of_trust; +} namespace tools { @@ -32,68 +33,5 @@ namespace tools std::string get_os_version_string(); bool create_directories_if_necessary(const std::string& path); std::error_code replace_file(const std::string& replacement_name, const std::string& replaced_name); - - inline crypto::hash get_proof_of_trust_hash(const nodetool::proof_of_trust& pot) - { - std::string s; - s.append(reinterpret_cast(&pot.peer_id), sizeof(pot.peer_id)); - s.append(reinterpret_cast(&pot.time), sizeof(pot.time)); - return crypto::cn_fast_hash(s.data(), s.size()); - } - - - class signal_handler - { - public: - template - static bool install(T t) - { -#if defined(WIN32) - bool r = TRUE == ::SetConsoleCtrlHandler(&win_handler, TRUE); - if (r) - { - m_handler = t; - } - return r; -#else - signal(SIGINT, posix_handler); - signal(SIGTERM, posix_handler); - m_handler = t; - return true; -#endif - } - - private: -#if defined(WIN32) - static BOOL WINAPI win_handler(DWORD type) - { - if (CTRL_C_EVENT == type || CTRL_BREAK_EVENT == type) - { - handle_signal(); - return TRUE; - } - else - { - LOG_PRINT_RED_L0("Got control signal " << type << ". Exiting without saving..."); - return FALSE; - } - return TRUE; - } -#else - static void posix_handler(int /*type*/) - { - handle_signal(); - } -#endif - - static void handle_signal() - { - static std::mutex m_mutex; - std::unique_lock lock(m_mutex); - m_handler(); - } - - private: - static std::function m_handler; - }; + crypto::hash get_proof_of_trust_hash(const nodetool::proof_of_trust& pot); } diff --git a/src/connectivity_tool/conn_tool.cpp b/src/connectivity_tool/conn_tool.cpp index a0e2ef0e..68fe8219 100644 --- a/src/connectivity_tool/conn_tool.cpp +++ b/src/connectivity_tool/conn_tool.cpp @@ -15,26 +15,27 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . - -#include "include_base_utils.h" -#include "version.h" - -using namespace epee; #include -#include "p2p/p2p_protocol_defs.h" + +// epee +#include "include_base_utils.h" +#include "net/http_client.h" +#include "net/levin_client.h" +#include "storages/http_abstract_invoke.h" +#include "storages/levin_abstract_invoke2.h" +#include "storages/portable_storage_template_helper.h" + #include "common/command_line.h" +#include "crypto/crypto.h" #include "cryptonote_core/cryptonote_core.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" -#include "net/levin_client.h" -#include "storages/levin_abstract_invoke2.h" -#include "cryptonote_core/cryptonote_core.h" -#include "storages/portable_storage_template_helper.h" -#include "crypto/crypto.h" -#include "storages/http_abstract_invoke.h" -#include "net/http_client.h" +#include "p2p/p2p_protocol_defs.h" +#include "rpc/core_rpc_server_commands_defs.h" +#include "version.h" namespace po = boost::program_options; using namespace cryptonote; +using namespace epee; using namespace nodetool; namespace diff --git a/src/crypto/hash-ops.h b/src/crypto/hash-ops.h index 1a567877..18d7147f 100644 --- a/src/crypto/hash-ops.h +++ b/src/crypto/hash-ops.h @@ -76,3 +76,6 @@ void hash_extra_jh(const void *data, size_t length, char *hash); void hash_extra_skein(const void *data, size_t length, char *hash); void tree_hash(const char (*hashes)[HASH_SIZE], size_t count, char *root_hash); +size_t tree_depth(size_t count); +void tree_branch(const char (*hashes)[HASH_SIZE], size_t count, char (*branch)[HASH_SIZE]); +void tree_hash_from_branch(const char (*branch)[HASH_SIZE], size_t depth, const char *leaf, const void *path, char *root_hash); diff --git a/src/crypto/hash.h b/src/crypto/hash.h index 4eaee990..5115e0f9 100644 --- a/src/crypto/hash.h +++ b/src/crypto/hash.h @@ -74,6 +74,14 @@ namespace crypto { tree_hash(reinterpret_cast(hashes), count, reinterpret_cast(&root_hash)); } + inline void tree_branch(const hash *hashes, std::size_t count, hash *branch) { + tree_branch(reinterpret_cast(hashes), count, reinterpret_cast(branch)); + } + + inline void tree_hash_from_branch(const hash *branch, std::size_t depth, const hash &leaf, const void *path, hash &root_hash) { + tree_hash_from_branch(reinterpret_cast(branch), depth, reinterpret_cast(&leaf), path, reinterpret_cast(&root_hash)); + } + } CRYPTO_MAKE_HASHABLE(hash) diff --git a/src/crypto/tree-hash.c b/src/crypto/tree-hash.c index 1d0f684e..feee98dd 100644 --- a/src/crypto/tree-hash.c +++ b/src/crypto/tree-hash.c @@ -51,3 +51,75 @@ void tree_hash(const char (*hashes)[HASH_SIZE], size_t count, char *root_hash) { cn_fast_hash(ints[0], 64, root_hash); } } + +size_t tree_depth(size_t count) { + size_t i; + size_t depth = 0; + assert(count > 0); + for (i = sizeof(size_t) << 2; i > 0; i >>= 1) { + if (count >> i > 0) { + count >>= i; + depth += i; + } + } + return depth; +} + +void tree_branch(const char (*hashes)[HASH_SIZE], size_t count, char (*branch)[HASH_SIZE]) { + size_t i, j; + size_t cnt = 1; + size_t depth = 0; + char (*ints)[HASH_SIZE]; + assert(count > 0); + for (i = sizeof(size_t) << 2; i > 0; i >>= 1) { + if (cnt << i <= count) { + cnt <<= i; + depth += i; + } + } + assert(cnt == 1ULL << depth); + assert(depth == tree_depth(count)); + ints = alloca((cnt - 1) * HASH_SIZE); + memcpy(ints, hashes + 1, (2 * cnt - count - 1) * HASH_SIZE); + for (i = 2 * cnt - count, j = 2 * cnt - count - 1; j < cnt - 1; i += 2, ++j) { + cn_fast_hash(hashes[i], 2 * HASH_SIZE, ints[j]); + } + assert(i == count); + while (depth > 0) { + assert(cnt == 1ULL << depth); + cnt >>= 1; + --depth; + memcpy(branch[depth], ints[0], HASH_SIZE); + for (i = 1, j = 0; j < cnt - 1; i += 2, ++j) { + cn_fast_hash(ints[i], 2 * HASH_SIZE, ints[j]); + } + } +} + +void tree_hash_from_branch(const char (*branch)[HASH_SIZE], size_t depth, const char *leaf, const void *path, char *root_hash) { + if (depth == 0) { + memcpy(root_hash, leaf, HASH_SIZE); + } else { + char buffer[2][HASH_SIZE]; + int from_leaf = 1; + char *leaf_path, *branch_path; + while (depth > 0) { + --depth; + if (path && (((const char *) path)[depth >> 3] & (1 << (depth & 7))) != 0) { + leaf_path = buffer[1]; + branch_path = buffer[0]; + } else { + leaf_path = buffer[0]; + branch_path = buffer[1]; + } + if (from_leaf) { + memcpy(leaf_path, leaf, HASH_SIZE); + from_leaf = 0; + } else { + cn_fast_hash(buffer, 2 * HASH_SIZE, leaf_path); + } + memcpy(branch_path, branch[depth], HASH_SIZE); + } + cn_fast_hash(buffer, 2 * HASH_SIZE, root_hash); + } +} diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index e7ce4bd3..1b6292fe 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -17,77 +17,133 @@ #pragma once -#define CRYPTONOTE_MAX_BLOCK_NUMBER 500000000 -#define CRYPTONOTE_MAX_BLOCK_SIZE 500000000 // block header blob limit, never used! -#define CRYPTONOTE_MAX_TX_SIZE 1000000000 -#define CRYPTONOTE_PUBLIC_ADDRESS_TEXTBLOB_VER 0 -#define CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX 6 // addresses start with "2" -#define CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW 10 -#define CURRENT_TRANSACTION_VERSION 1 -#define CURRENT_BLOCK_MAJOR_VERSION 1 -#define CURRENT_BLOCK_MINOR_VERSION 0 -#define CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT 60*60*2 +#include -#define BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW 60 +namespace cryptonote { +namespace parameters { + +const uint64_t CRYPTONOTE_MAX_BLOCK_NUMBER = 500000000; +const size_t CRYPTONOTE_MAX_BLOCK_BLOB_SIZE = 500000000; +const size_t CRYPTONOTE_MAX_TX_SIZE = 1000000000; +const uint64_t CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 6; // addresses start with "2" +const size_t CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW = 10; +const uint64_t CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT = 60 * 60 * 2; + +const size_t BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW = 60; // MONEY_SUPPLY - total number coins to be generated -#define MONEY_SUPPLY ((uint64_t)(-1)) -#define EMISSION_SPEED_FACTOR (18) +const uint64_t MONEY_SUPPLY = static_cast(-1); +const unsigned EMISSION_SPEED_FACTOR = 18; +static_assert(EMISSION_SPEED_FACTOR <= 8 * sizeof(uint64_t), "Bad EMISSION_SPEED_FACTOR"); -#define CRYPTONOTE_REWARD_BLOCKS_WINDOW 100 -#define CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE 10000 //size of block (bytes) after which reward for block calculated using block size -#define CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE 600 -#define CRYPTONOTE_DISPLAY_DECIMAL_POINT 8 +const size_t CRYPTONOTE_REWARD_BLOCKS_WINDOW = 100; +const size_t CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE = 10000; //size of block (bytes) after which reward for block calculated using block size +const size_t CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE = 600; +const size_t CRYPTONOTE_DISPLAY_DECIMAL_POINT = 8; // COIN - number of smallest units in one coin -#define COIN ((uint64_t)100000000) // pow(10, 8) -#define MINIMUM_FEE ((uint64_t)1000000000) // pow(10, 9) -#define DEFAULT_DUST_THRESHOLD ((uint64_t)1000000) // pow(10, 6) +const uint64_t COIN = UINT64_C(100000000); // pow(10, 8) +const uint64_t MINIMUM_FEE = UINT64_C(1000000); // pow(10, 6) +const uint64_t DEFAULT_DUST_THRESHOLD = UINT64_C(1000000); // pow(10, 6) +const uint64_t DIFFICULTY_TARGET = 120; // seconds +const uint64_t EXPECTED_NUMBER_OF_BLOCKS_PER_DAY = 24 * 60 * 60 / DIFFICULTY_TARGET; +const size_t DIFFICULTY_WINDOW = EXPECTED_NUMBER_OF_BLOCKS_PER_DAY; // blocks +const size_t DIFFICULTY_CUT = 60; // timestamps to cut after sorting +const size_t DIFFICULTY_LAG = 15; // !!! +static_assert(2 * DIFFICULTY_CUT <= DIFFICULTY_WINDOW - 2, "Bad DIFFICULTY_WINDOW or DIFFICULTY_CUT"); -#define DIFFICULTY_TARGET 120 // seconds -#define DIFFICULTY_WINDOW 720 // blocks -#define DIFFICULTY_LAG 15 // !!! -#define DIFFICULTY_CUT 60 // timestamps to cut after sorting -#define DIFFICULTY_BLOCKS_COUNT DIFFICULTY_WINDOW + DIFFICULTY_LAG +const size_t MAX_BLOCK_SIZE_INITIAL = 20 * 1024; +const uint64_t MAX_BLOCK_SIZE_GROWTH_SPEED_NUMERATOR = 100 * 1024; +const uint64_t MAX_BLOCK_SIZE_GROWTH_SPEED_DENOMINATOR = 365 * 24 * 60 * 60 / DIFFICULTY_TARGET; +const uint64_t CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS = 1; +const uint64_t CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS = DIFFICULTY_TARGET * CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS; -#define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS DIFFICULTY_TARGET * CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS -#define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS 1 +const uint64_t CRYPTONOTE_MEMPOOL_TX_LIVETIME = 60 * 60 * 24; //seconds, one day +const uint64_t CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME = 60 * 60 * 24 * 7; //seconds, one week +const unsigned UPGRADE_VOTING_THRESHOLD = 90; // percent +const size_t UPGRADE_VOTING_WINDOW = EXPECTED_NUMBER_OF_BLOCKS_PER_DAY; // blocks +const size_t UPGRADE_WINDOW = EXPECTED_NUMBER_OF_BLOCKS_PER_DAY; // blocks +static_assert(0 < UPGRADE_VOTING_THRESHOLD && UPGRADE_VOTING_THRESHOLD <= 100, "Bad UPGRADE_VOTING_THRESHOLD"); +static_assert(UPGRADE_VOTING_WINDOW > 1, "Bad UPGRADE_VOTING_WINDOW"); -#define DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN DIFFICULTY_TARGET //just alias +const char CRYPTONOTE_BLOCKCHAINDATA_FILENAME[] = "blockchain.bin"; // Obsolete blockchain format +const char CRYPTONOTE_BLOCKS_FILENAME[] = "blocks.dat"; +const char CRYPTONOTE_BLOCKINDEXES_FILENAME[] = "blockindexes.dat"; +const char CRYPTONOTE_BLOCKSCACHE_FILENAME[] = "blockscache.dat"; +const char CRYPTONOTE_POOLDATA_FILENAME[] = "poolstate.bin"; +const char P2P_NET_DATA_FILENAME[] = "p2pstate.bin"; +const char MINER_CONFIG_FILE_NAME[] = "miner_conf.json"; +} // parameters +const char CRYPTONOTE_NAME[] = "bytecoin"; -#define BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT 10000 //by default, blocks ids count in synchronizing -#define BLOCKS_SYNCHRONIZING_DEFAULT_COUNT 200 //by default, blocks count in blocks downloading -#define CRYPTONOTE_PROTOCOL_HOP_RELAX_COUNT 3 //value of hop, after which we use only announce of new block +const uint8_t CURRENT_TRANSACTION_VERSION = 1; +const uint8_t BLOCK_MAJOR_VERSION_1 = 1; +const uint8_t BLOCK_MAJOR_VERSION_2 = 2; +const uint8_t BLOCK_MINOR_VERSION_0 = 0; +const uint8_t BLOCK_MINOR_VERSION_1 = 1; +const size_t BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT = 10000; //by default, blocks ids count in synchronizing +const size_t BLOCKS_SYNCHRONIZING_DEFAULT_COUNT = 200; //by default, blocks count in blocks downloading +const size_t COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT = 1000; -#define P2P_DEFAULT_PORT 8080 -#define RPC_DEFAULT_PORT 8081 -#define COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT 1000 +const int P2P_DEFAULT_PORT = 8080; +const int RPC_DEFAULT_PORT = 8081; -#define P2P_LOCAL_WHITE_PEERLIST_LIMIT 1000 -#define P2P_LOCAL_GRAY_PEERLIST_LIMIT 5000 +const size_t P2P_LOCAL_WHITE_PEERLIST_LIMIT = 1000; +const size_t P2P_LOCAL_GRAY_PEERLIST_LIMIT = 5000; -#define P2P_DEFAULT_CONNECTIONS_COUNT 8 -#define P2P_DEFAULT_HANDSHAKE_INTERVAL 60 //secondes -#define P2P_DEFAULT_PACKET_MAX_SIZE 50000000 //50000000 bytes maximum packet size -#define P2P_DEFAULT_PEERS_IN_HANDSHAKE 250 -#define P2P_DEFAULT_CONNECTION_TIMEOUT 5000 //5 seconds -#define P2P_DEFAULT_PING_CONNECTION_TIMEOUT 2000 //2 seconds -#define P2P_DEFAULT_INVOKE_TIMEOUT 60*2*1000 //2 minutes -#define P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT 5000 //5 seconds -#define P2P_STAT_TRUSTED_PUB_KEY "8f80f9a5a434a9f1510d13336228debfee9c918ce505efe225d8c94d045fa115" -#define P2P_DEFAULT_WHITELIST_CONNECTIONS_PERCENT 70 +const uint32_t P2P_DEFAULT_CONNECTIONS_COUNT = 8; +const uint32_t P2P_DEFAULT_HANDSHAKE_INTERVAL = 60; // seconds +const uint32_t P2P_DEFAULT_PACKET_MAX_SIZE = 50000000; // 50000000 bytes maximum packet size +const uint32_t P2P_DEFAULT_PEERS_IN_HANDSHAKE = 250; +const uint32_t P2P_DEFAULT_CONNECTION_TIMEOUT = 5000; // 5 seconds +const uint32_t P2P_DEFAULT_PING_CONNECTION_TIMEOUT = 2000; // 2 seconds +const uint64_t P2P_DEFAULT_INVOKE_TIMEOUT = 60 * 2 * 1000; // 2 minutes +const size_t P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT = 5000; // 5 seconds +const char P2P_STAT_TRUSTED_PUB_KEY[] = "8f80f9a5a434a9f1510d13336228debfee9c918ce505efe225d8c94d045fa115"; +const size_t P2P_DEFAULT_WHITELIST_CONNECTIONS_PERCENT = 70; + +const unsigned THREAD_STACK_SIZE = 5 * 1024 * 1024; + +const char* const SEED_NODES[] = { + "seed.bytecoin.org:8080", + "85.25.201.95:8080", + "85.25.196.145:8080", + "85.25.196.146:8080", + "85.25.196.144:8080", + "5.199.168.138:8080", + "62.75.236.152:8080", + "85.25.194.245:8080", + "95.211.224.160:8080", + "144.76.200.44:8080" +}; + +struct CheckpointData { + uint64_t height; + const char* blockId; +}; + +const CheckpointData CHECKPOINTS[] = { + {79000, "cae33204e624faeb64938d80073bb7bbacc27017dc63f36c5c0f313cad455a02"}, + {140000, "993059fb6ab92db7d80d406c67a52d9c02d873ca34b6290a12b744c970208772"}, + {200000, "a5f74c7542077df6859f48b5b1f9c3741f29df38f91a47e14c94b5696e6c3073"}, + {230580, "32bd7cb6c68a599cf2861941f29002a5e203522b9af54f08dfced316f6459103"}, + {260000, "f68e70b360ca194f48084da7a7fd8e0251bbb4b5587f787ca65a6f5baf3f5947"}, + {300000, "8e80861713f68354760dc10ea6ea79f5f3ff28f39b3f0835a8637463b09d70ff"}, + {390285, "e00bdc9bf407aeace2f3109de11889ed25894bf194231d075eddaec838097eb7"}, + {417000, "2dc96f8fc4d4a4d76b3ed06722829a7ab09d310584b8ecedc9b578b2c458a69f"}, + {427193, "00feabb08f2d5759ed04fd6b799a7513187478696bba2db2af10d4347134e311"}, + {453537, "d17de6916c5aa6ffcae575309c80b0f8fdcd0a84b5fa8e41a841897d4b5a4e97"}, + {462250, "13468d210a5ec884cf839f0259f247ccf3efef0414ac45172033d32c739beb3e"}, + {468000, "251bcbd398b1f593193a7210934a3d87f692b2cb0c45206150f59683dd7e9ba1"}, + {480200, "363544ac9920c778b815c2fdbcbca70a0d79b21f662913a42da9b49e859f0e5b"}, + {484500, "5cdf2101a0a62a0ab2a1ca0c15a6212b21f6dbdc42a0b7c0bcf65ca40b7a14fb"}, + {506000, "3d54c1132f503d98d3f0d78bb46a4503c1a19447cb348361a2232e241cb45a3c"}, + {544000, "f69dc61b6a63217f32fa64d5d0f9bd920873f57dfd79ebe1d7d6fb1345b56fe0"} +}; +} // cryptonote #define ALLOW_DEBUG_COMMANDS - -#define CRYPTONOTE_NAME "bytecoin" -#define CRYPTONOTE_POOLDATA_FILENAME "poolstate.bin" -#define CRYPTONOTE_BLOCKCHAINDATA_FILENAME "blockchain.bin" -#define CRYPTONOTE_BLOCKCHAINDATA_TEMP_FILENAME "blockchain.bin.tmp" -#define P2P_NET_DATA_FILENAME "p2pstate.bin" -#define MINER_CONFIG_FILE_NAME "miner_conf.json" - -#define THREAD_STACK_SIZE 5 * 1024 * 1024 diff --git a/src/cryptonote_core/AccountKVSerialization.h b/src/cryptonote_core/AccountKVSerialization.h new file mode 100644 index 00000000..31d65f76 --- /dev/null +++ b/src/cryptonote_core/AccountKVSerialization.h @@ -0,0 +1,113 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include "cryptonote_core/account.h" + +// epee +#include "serialization/keyvalue_serialization.h" + +namespace cryptonote { + template struct AccountPublicAddressSerializer; + template struct AccountKeysSerializer; + template struct AccountBaseSerializer; + + template<> + struct AccountPublicAddressSerializer { + const AccountPublicAddress& m_account_address; + + AccountPublicAddressSerializer(const AccountPublicAddress& account_address) : m_account_address(account_address) { + } + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(m_account_address.m_spendPublicKey, "m_spend_public_key") + KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(m_account_address.m_viewPublicKey, "m_view_public_key") + END_KV_SERIALIZE_MAP() + }; + + template<> + struct AccountPublicAddressSerializer { + AccountPublicAddress& m_account_address; + + AccountPublicAddressSerializer(AccountPublicAddress& account_address) : m_account_address(account_address) { + } + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(m_account_address.m_spendPublicKey, "m_spend_public_key") + KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(m_account_address.m_viewPublicKey, "m_view_public_key") + END_KV_SERIALIZE_MAP() + }; + + template<> + struct AccountKeysSerializer { + const account_keys& m_keys; + + AccountKeysSerializer(const account_keys& keys) : m_keys(keys) { + } + + BEGIN_KV_SERIALIZE_MAP() + AccountPublicAddressSerializer addressSerializer(this_ref.m_keys.m_account_address); + epee::serialization::selector::serialize(addressSerializer, stg, hparent_section, "m_account_address"); + KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(m_keys.m_spend_secret_key, "m_spend_secret_key") + KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(m_keys.m_view_secret_key, "m_view_secret_key") + END_KV_SERIALIZE_MAP() + }; + + template<> + struct AccountKeysSerializer { + account_keys& m_keys; + + AccountKeysSerializer(account_keys& keys) : m_keys(keys) { + } + + BEGIN_KV_SERIALIZE_MAP() + AccountPublicAddressSerializer addressSerializer(this_ref.m_keys.m_account_address); + epee::serialization::selector::serialize(addressSerializer, stg, hparent_section, "m_account_address"); + KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(m_keys.m_spend_secret_key, "m_spend_secret_key") + KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(m_keys.m_view_secret_key, "m_view_secret_key") + END_KV_SERIALIZE_MAP() + }; + + template<> + struct AccountBaseSerializer { + const account_base& m_account; + + AccountBaseSerializer(const account_base& account) : m_account(account) { + } + + BEGIN_KV_SERIALIZE_MAP() + AccountKeysSerializer keysSerializer(this_ref.m_account.m_keys); + epee::serialization::selector::serialize(keysSerializer, stg, hparent_section, "m_keys"); + KV_SERIALIZE_N(m_account.m_creation_timestamp, "m_creation_timestamp") + END_KV_SERIALIZE_MAP() + }; + + template<> + struct AccountBaseSerializer { + account_base& m_account; + + AccountBaseSerializer(account_base& account) : m_account(account) { + } + + BEGIN_KV_SERIALIZE_MAP() + AccountKeysSerializer keysSerializer(this_ref.m_account.m_keys); + epee::serialization::selector::serialize(keysSerializer, stg, hparent_section, "m_keys"); + KV_SERIALIZE_N(m_account.m_creation_timestamp, "m_creation_timestamp") + END_KV_SERIALIZE_MAP() + }; +} diff --git a/src/cryptonote_core/BlockIndex.cpp b/src/cryptonote_core/BlockIndex.cpp new file mode 100644 index 00000000..984a8bb6 --- /dev/null +++ b/src/cryptonote_core/BlockIndex.cpp @@ -0,0 +1,87 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "BlockIndex.h" +#include + +namespace CryptoNote +{ + crypto::hash BlockIndex::getBlockId(uint64_t height) const { + if (height >= m_container.size()) + return boost::value_initialized(); + return m_container[static_cast(height)]; + } + + bool BlockIndex::getBlockIds(uint64_t startHeight, size_t maxCount, std::list& items) const { + if (startHeight >= m_container.size()) + return false; + + for (size_t i = startHeight; i < (startHeight + maxCount) && i < m_container.size(); ++i) { + items.push_back(m_container[i]); + } + + return true; + } + + + bool BlockIndex::findSupplement(const std::list& ids, uint64_t& offset) const { + + for (const auto& id : ids) { + if (getBlockHeight(id, offset)) + return true; + } + + return false; + } + + bool BlockIndex::getShortChainHistory(std::list& ids) const { + size_t i = 0; + size_t current_multiplier = 1; + size_t sz = size(); + + if (!sz) + return true; + + size_t current_back_offset = 1; + bool genesis_included = false; + + while (current_back_offset < sz) { + ids.push_back(m_container[sz - current_back_offset]); + if (sz - current_back_offset == 0) + genesis_included = true; + if (i < 10) { + ++current_back_offset; + } else { + current_back_offset += current_multiplier *= 2; + } + ++i; + } + + if (!genesis_included) + ids.push_back(m_container[0]); + + return true; + } + + crypto::hash BlockIndex::getTailId() const { + if (m_container.empty()) + return boost::value_initialized(); + return m_container.back(); + } + + +} diff --git a/src/cryptonote_core/BlockIndex.h b/src/cryptonote_core/BlockIndex.h new file mode 100644 index 00000000..382e6e01 --- /dev/null +++ b/src/cryptonote_core/BlockIndex.h @@ -0,0 +1,90 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +// multi index +#include +#include +#include + +#include "crypto/hash.h" +#include + +namespace CryptoNote +{ + class BlockIndex { + + public: + + BlockIndex() : + m_index(m_container.get<1>()) {} + + void pop() { + m_container.pop_back(); + } + + // returns true if new element was inserted, false if already exists + bool push(const crypto::hash& h) { + auto result = m_container.push_back(h); + return result.second; + } + + bool hasBlock(const crypto::hash& h) const { + return m_index.find(h) != m_index.end(); + } + + bool getBlockHeight(const crypto::hash& h, uint64_t& height) const { + auto hi = m_index.find(h); + if (hi == m_index.end()) + return false; + + height = std::distance(m_container.begin(), m_container.project<0>(hi)); + return true; + } + + size_t size() const { + return m_container.size(); + } + + void clear() { + m_container.clear(); + } + + crypto::hash getBlockId(uint64_t height) const; + bool getBlockIds(uint64_t startHeight, size_t maxCount, std::list& items) const; + bool findSupplement(const std::list& ids, uint64_t& offset) const; + bool getShortChainHistory(std::list& ids) const; + crypto::hash getTailId() const; + + template void serialize(Archive& ar, const unsigned int version) { + ar & m_container; + } + + private: + + typedef boost::multi_index_container < + crypto::hash, + boost::multi_index::indexed_by< + boost::multi_index::random_access<>, + boost::multi_index::hashed_unique> + > + > ContainerT; + + ContainerT m_container; + ContainerT::nth_index<1>::type& m_index; + + }; +} diff --git a/src/cryptonote_core/Currency.cpp b/src/cryptonote_core/Currency.cpp new file mode 100644 index 00000000..aac4338b --- /dev/null +++ b/src/cryptonote_core/Currency.cpp @@ -0,0 +1,435 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "Currency.h" + +#include +#include + +// epee +#include "include_base_utils.h" +#include "string_tools.h" + +#include "common/base58.h" +#include "common/int-util.h" +#include "cryptonote_core/account.h" +#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_core/UpgradeDetector.h" + +namespace cryptonote { + bool Currency::init() { + bool r; + r = generateGenesisBlock(); + CHECK_AND_ASSERT_MES(r, false, "Failed to generate genesis block"); + + r = get_block_hash(m_genesisBlock, m_genesisBlockHash); + CHECK_AND_ASSERT_MES(r, false, "Failed to get genesis block hash"); + + if (isTestnet()) { + m_blocksFileName = "testnet_" + m_blocksFileName; + m_blocksCacheFileName = "testnet_" + m_blocksCacheFileName; + m_blockIndexesFileName = "testnet_" + m_blockIndexesFileName; + m_txPoolFileName = "testnet_" + m_txPoolFileName; + } + + return true; + } + + bool Currency::generateGenesisBlock() { + m_genesisBlock = boost::value_initialized(); + + //account_public_address ac = boost::value_initialized(); + //std::vector sz; + //constructMinerTx(0, 0, 0, 0, 0, ac, m_genesisBlock.minerTx); // zero fee in genesis + //blobdata txb = tx_to_blob(m_genesisBlock.minerTx); + //std::string hex_tx_represent = string_tools::buff_to_hex_nodelimer(txb); + + // Hard code coinbase tx in genesis block, because through generating tx use random, but genesis should be always the same + std::string genesisCoinbaseTxHex = "010a01ff0001ffffffffffff0f029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121013c086a48c15fb637a96991bc6d53caf77068b5ba6eeb3c82357228c49790584a"; + + blobdata minerTxBlob; + epee::string_tools::parse_hexstr_to_binbuff(genesisCoinbaseTxHex, minerTxBlob); + bool r = parse_and_validate_tx_from_blob(minerTxBlob, m_genesisBlock.minerTx); + CHECK_AND_ASSERT_MES(r, false, "failed to parse coinbase tx from hard coded blob"); + m_genesisBlock.majorVersion = BLOCK_MAJOR_VERSION_1; + m_genesisBlock.minorVersion = BLOCK_MINOR_VERSION_0; + m_genesisBlock.timestamp = 0; + m_genesisBlock.nonce = 70; + if (m_testnet) { + ++m_genesisBlock.nonce; + } + //miner::find_nonce_for_given_block(bl, 1, 0); + + return true; + } + + bool Currency::getBlockReward(size_t medianSize, size_t currentBlockSize, uint64_t alreadyGeneratedCoins, + uint64_t fee, bool penalizeFee, uint64_t& reward, int64_t& emissionChange) const { + assert(alreadyGeneratedCoins <= m_moneySupply); + assert(m_emissionSpeedFactor > 0 && m_emissionSpeedFactor <= 8 * sizeof(uint64_t)); + + uint64_t baseReward = (m_moneySupply - alreadyGeneratedCoins) >> m_emissionSpeedFactor; + + medianSize = std::max(medianSize, m_blockGrantedFullRewardZone); + if (currentBlockSize > UINT64_C(2) * medianSize) { + LOG_PRINT_L4("Block cumulative size is too big: " << currentBlockSize << ", expected less than " << 2 * medianSize); + return false; + } + + uint64_t penalizedBaseReward = getPenalizedAmount(baseReward, medianSize, currentBlockSize); + uint64_t penalizedFee = penalizeFee ? getPenalizedAmount(fee, medianSize, currentBlockSize) : fee; + + emissionChange = penalizedBaseReward - (fee - penalizedFee); + reward = penalizedBaseReward + penalizedFee; + + return true; + } + + size_t Currency::maxBlockCumulativeSize(uint64_t height) const { + assert(height <= std::numeric_limits::max() / m_maxBlockSizeGrowthSpeedNumerator); + size_t maxSize = static_cast(m_maxBlockSizeInitial + + (height * m_maxBlockSizeGrowthSpeedNumerator) / m_maxBlockSizeGrowthSpeedDenominator); + assert(maxSize >= m_maxBlockSizeInitial); + return maxSize; + } + + bool Currency::constructMinerTx(size_t height, size_t medianSize, uint64_t alreadyGeneratedCoins, size_t currentBlockSize, + uint64_t fee, const AccountPublicAddress& minerAddress, Transaction& tx, + const blobdata& extraNonce/* = blobdata()*/, size_t maxOuts/* = 1*/, + bool penalizeFee/* = false*/) const { + tx.vin.clear(); + tx.vout.clear(); + tx.extra.clear(); + + KeyPair txkey = KeyPair::generate(); + add_tx_pub_key_to_extra(tx, txkey.pub); + if (!extraNonce.empty()) { + if (!add_extra_nonce_to_tx_extra(tx.extra, extraNonce)) { + return false; + } + } + + TransactionInputGenerate in; + in.height = height; + + uint64_t blockReward; + int64_t emissionChange; + if (!getBlockReward(medianSize, currentBlockSize, alreadyGeneratedCoins, fee, penalizeFee, blockReward, emissionChange)) { + LOG_PRINT_L0("Block is too big"); + return false; + } +#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) + LOG_PRINT_L1("Creating block template: reward " << blockReward << ", fee " << fee); +#endif + + std::vector outAmounts; + decompose_amount_into_digits(blockReward, m_defaultDustThreshold, + [&outAmounts](uint64_t a_chunk) { outAmounts.push_back(a_chunk); }, + [&outAmounts](uint64_t a_dust) { outAmounts.push_back(a_dust); }); + + CHECK_AND_ASSERT_MES(1 <= maxOuts, false, "max_out must be non-zero"); + while (maxOuts < outAmounts.size()) { + outAmounts[outAmounts.size() - 2] += outAmounts.back(); + outAmounts.resize(outAmounts.size() - 1); + } + + uint64_t summaryAmounts = 0; + for (size_t no = 0; no < outAmounts.size(); no++) { + crypto::key_derivation derivation = boost::value_initialized(); + crypto::public_key outEphemeralPubKey = boost::value_initialized(); + bool r = crypto::generate_key_derivation(minerAddress.m_viewPublicKey, txkey.sec, derivation); + CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to generate_key_derivation(" << + minerAddress.m_viewPublicKey << ", " << txkey.sec << ")"); + + r = crypto::derive_public_key(derivation, no, minerAddress.m_spendPublicKey, outEphemeralPubKey); + CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to derive_public_key(" << derivation << ", " << + no << ", "<< minerAddress.m_spendPublicKey << ")"); + + TransactionOutputToKey tk; + tk.key = outEphemeralPubKey; + + TransactionOutput out; + summaryAmounts += out.amount = outAmounts[no]; + out.target = tk; + tx.vout.push_back(out); + } + + CHECK_AND_ASSERT_MES(summaryAmounts == blockReward, false, + "Failed to construct miner tx, summaryAmounts = " << summaryAmounts << " not equal blockReward = " << blockReward); + + tx.version = CURRENT_TRANSACTION_VERSION; + //lock + tx.unlockTime = height + m_minedMoneyUnlockWindow; + tx.vin.push_back(in); + return true; + } + + std::string Currency::accountAddressAsString(const account_base& account) const { + return getAccountAddressAsStr(m_publicAddressBase58Prefix, account.get_keys().m_account_address); + } + + bool Currency::parseAccountAddressString(const std::string& str, AccountPublicAddress& addr) const { + uint64_t prefix; + if (!cryptonote::parseAccountAddressString(prefix, addr, str)) { + return false; + } + + if (prefix != m_publicAddressBase58Prefix) { + LOG_PRINT_L1("Wrong address prefix: " << prefix << ", expected " << m_publicAddressBase58Prefix); + return false; + } + + return true; + } + + std::string Currency::formatAmount(uint64_t amount) const { + std::string s = std::to_string(amount); + if (s.size() < m_numberOfDecimalPlaces + 1) { + s.insert(0, m_numberOfDecimalPlaces + 1 - s.size(), '0'); + } + s.insert(s.size() - m_numberOfDecimalPlaces, "."); + return s; + } + + bool Currency::parseAmount(const std::string& str, uint64_t& amount) const { + std::string strAmount = str; + boost::algorithm::trim(strAmount); + + size_t pointIndex = strAmount.find_first_of('.'); + size_t fractionSize; + if (std::string::npos != pointIndex) { + fractionSize = strAmount.size() - pointIndex - 1; + while (m_numberOfDecimalPlaces < fractionSize && '0' == strAmount.back()) { + strAmount.erase(strAmount.size() - 1, 1); + --fractionSize; + } + if (m_numberOfDecimalPlaces < fractionSize) { + return false; + } + strAmount.erase(pointIndex, 1); + } else { + fractionSize = 0; + } + + if (strAmount.empty()) { + return false; + } + + if (fractionSize < m_numberOfDecimalPlaces) { + strAmount.append(m_numberOfDecimalPlaces - fractionSize, '0'); + } + + return epee::string_tools::get_xtype_from_string(amount, strAmount); + } + + difficulty_type Currency::nextDifficulty(std::vector timestamps, + std::vector cumulativeDifficulties) const { + assert(m_difficultyWindow >= 2); + + if (timestamps.size() > m_difficultyWindow) { + timestamps.resize(m_difficultyWindow); + cumulativeDifficulties.resize(m_difficultyWindow); + } + + size_t length = timestamps.size(); + assert(length == cumulativeDifficulties.size()); + assert(length <= m_difficultyWindow); + if (length <= 1) { + return 1; + } + + sort(timestamps.begin(), timestamps.end()); + + size_t cutBegin, cutEnd; + assert(2 * m_difficultyCut <= m_difficultyWindow - 2); + if (length <= m_difficultyWindow - 2 * m_difficultyCut) { + cutBegin = 0; + cutEnd = length; + } else { + cutBegin = (length - (m_difficultyWindow - 2 * m_difficultyCut) + 1) / 2; + cutEnd = cutBegin + (m_difficultyWindow - 2 * m_difficultyCut); + } + assert(/*cut_begin >= 0 &&*/ cutBegin + 2 <= cutEnd && cutEnd <= length); + uint64_t timeSpan = timestamps[cutEnd - 1] - timestamps[cutBegin]; + if (timeSpan == 0) { + timeSpan = 1; + } + + difficulty_type totalWork = cumulativeDifficulties[cutEnd - 1] - cumulativeDifficulties[cutBegin]; + assert(totalWork > 0); + + uint64_t low, high; + low = mul128(totalWork, m_difficultyTarget, &high); + if (high != 0 || low + timeSpan - 1 < low) { + return 0; + } + + return (low + timeSpan - 1) / timeSpan; + } + + bool Currency::checkProofOfWorkV1(crypto::cn_context& context, const Block& block, difficulty_type currentDiffic, + crypto::hash& proofOfWork) const { + if (BLOCK_MAJOR_VERSION_1 != block.majorVersion) { + return false; + } + + if (!get_block_longhash(context, block, proofOfWork)) { + return false; + } + + return check_hash(proofOfWork, currentDiffic); + } + + bool Currency::checkProofOfWorkV2(crypto::cn_context& context, const Block& block, difficulty_type currentDiffic, + crypto::hash& proofOfWork) const { + if (BLOCK_MAJOR_VERSION_2 != block.majorVersion) { + return false; + } + + if (!get_block_longhash(context, block, proofOfWork)) { + return false; + } + + if (!check_hash(proofOfWork, currentDiffic)) { + return false; + } + + tx_extra_merge_mining_tag mmTag; + if (!get_mm_tag_from_extra(block.parentBlock.minerTx.extra, mmTag)) { + LOG_ERROR("merge mining tag wasn't found in extra of the parent block miner transaction"); + return false; + } + + if (8 * sizeof(m_genesisBlockHash) < block.parentBlock.blockchainBranch.size()) { + return false; + } + + crypto::hash auxBlockHeaderHash; + if (!get_aux_block_header_hash(block, auxBlockHeaderHash)) { + return false; + } + + crypto::hash auxBlocksMerkleRoot; + crypto::tree_hash_from_branch(block.parentBlock.blockchainBranch.data(), block.parentBlock.blockchainBranch.size(), + auxBlockHeaderHash, &m_genesisBlockHash, auxBlocksMerkleRoot); + CHECK_AND_NO_ASSERT_MES(auxBlocksMerkleRoot == mmTag.merkle_root, false, "Aux block hash wasn't found in merkle tree"); + + return true; + } + + bool Currency::checkProofOfWork(crypto::cn_context& context, const Block& block, difficulty_type currentDiffic, crypto::hash& proofOfWork) const { + switch (block.majorVersion) { + case BLOCK_MAJOR_VERSION_1: return checkProofOfWorkV1(context, block, currentDiffic, proofOfWork); + case BLOCK_MAJOR_VERSION_2: return checkProofOfWorkV2(context, block, currentDiffic, proofOfWork); + } + + CHECK_AND_ASSERT_MES(false, false, "Unknown block major version: " << block.majorVersion << "." << block.minorVersion); + } + + CurrencyBuilder::CurrencyBuilder() { + maxBlockNumber(parameters::CRYPTONOTE_MAX_BLOCK_NUMBER); + maxBlockBlobSize(parameters::CRYPTONOTE_MAX_BLOCK_BLOB_SIZE); + maxTxSize(parameters::CRYPTONOTE_MAX_TX_SIZE); + publicAddressBase58Prefix(parameters::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX); + minedMoneyUnlockWindow(parameters::CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW); + + timestampCheckWindow(parameters::BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW); + blockFutureTimeLimit(parameters::CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT); + + moneySupply(parameters::MONEY_SUPPLY); + emissionSpeedFactor(parameters::EMISSION_SPEED_FACTOR); + + rewardBlocksWindow(parameters::CRYPTONOTE_REWARD_BLOCKS_WINDOW); + blockGrantedFullRewardZone(parameters::CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE); + minerTxBlobReservedSize(parameters::CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE); + + numberOfDecimalPlaces(parameters::CRYPTONOTE_DISPLAY_DECIMAL_POINT); + + mininumFee(parameters::MINIMUM_FEE); + defaultDustThreshold(parameters::DEFAULT_DUST_THRESHOLD); + + difficultyTarget(parameters::DIFFICULTY_TARGET); + difficultyWindow(parameters::DIFFICULTY_WINDOW); + difficultyLag(parameters::DIFFICULTY_LAG); + difficultyCut(parameters::DIFFICULTY_CUT); + + maxBlockSizeInitial(parameters::MAX_BLOCK_SIZE_INITIAL); + maxBlockSizeGrowthSpeedNumerator(parameters::MAX_BLOCK_SIZE_GROWTH_SPEED_NUMERATOR); + maxBlockSizeGrowthSpeedDenominator(parameters::MAX_BLOCK_SIZE_GROWTH_SPEED_DENOMINATOR); + + lockedTxAllowedDeltaSeconds(parameters::CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS); + lockedTxAllowedDeltaBlocks(parameters::CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS); + + mempoolTxLiveTime(parameters::CRYPTONOTE_MEMPOOL_TX_LIVETIME); + mempoolTxFromAltBlockLiveTime(parameters::CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME); + + upgradeHeight(UpgradeDetectorBase::UNDEF_HEIGHT); + upgradeVotingThreshold(parameters::UPGRADE_VOTING_THRESHOLD); + upgradeVotingWindow(parameters::UPGRADE_VOTING_WINDOW); + upgradeWindow(parameters::UPGRADE_WINDOW); + + blocksFileName(parameters::CRYPTONOTE_BLOCKS_FILENAME); + blocksCacheFileName(parameters::CRYPTONOTE_BLOCKSCACHE_FILENAME); + blockIndexesFileName(parameters::CRYPTONOTE_BLOCKINDEXES_FILENAME); + txPoolFileName(parameters::CRYPTONOTE_POOLDATA_FILENAME); + + testnet(false); + } + + CurrencyBuilder& CurrencyBuilder::emissionSpeedFactor(unsigned int val) { + if (val <= 0 || val > 8 * sizeof(uint64_t)) { + throw std::invalid_argument("val at emissionSpeedFactor()"); + } + + m_currency.m_emissionSpeedFactor = val; + return *this; + } + + CurrencyBuilder& CurrencyBuilder::numberOfDecimalPlaces(size_t val) { + m_currency.m_numberOfDecimalPlaces = val; + m_currency.m_coin = 1; + for (size_t i = 0; i < m_currency.m_numberOfDecimalPlaces; ++i) { + m_currency.m_coin *= 10; + } + + return *this; + } + + CurrencyBuilder& CurrencyBuilder::difficultyWindow(size_t val) { + if (val < 2) { + throw std::invalid_argument("val at difficultyWindow()"); + } + m_currency.m_difficultyWindow = val; + return *this; + } + + CurrencyBuilder& CurrencyBuilder::upgradeVotingThreshold(unsigned int val) { + if (val <= 0 || val > 100) { + throw std::invalid_argument("val at upgradeVotingThreshold()"); + } + m_currency.m_upgradeVotingThreshold = val; + return *this; + } + + CurrencyBuilder& CurrencyBuilder::upgradeWindow(size_t val) { + if (val <= 0) { + throw std::invalid_argument("val at upgradeWindow()"); + } + m_currency.m_upgradeWindow = val; + return *this; + } +} diff --git a/src/cryptonote_core/Currency.h b/src/cryptonote_core/Currency.h new file mode 100644 index 00000000..bbdffd39 --- /dev/null +++ b/src/cryptonote_core/Currency.h @@ -0,0 +1,238 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include +#include +#include + +#include + +#include "crypto/hash.h" +#include "cryptonote_core/cryptonote_basic.h" +#include "cryptonote_core/difficulty.h" +#include "cryptonote_config.h" +#include "cryptonote_protocol/blobdatatype.h" + +namespace cryptonote +{ + class Currency { + public: + uint64_t maxBlockHeight() const { return m_maxBlockHeight; } + size_t maxBlockBlobSize() const { return m_maxBlockBlobSize; } + size_t maxTxSize() const { return m_maxTxSize; } + uint64_t publicAddressBase58Prefix() const { return m_publicAddressBase58Prefix; } + size_t minedMoneyUnlockWindow() const { return m_minedMoneyUnlockWindow; } + + size_t timestampCheckWindow() const { return m_timestampCheckWindow; } + uint64_t blockFutureTimeLimit() const { return m_blockFutureTimeLimit; } + + uint64_t moneySupply() const { return m_moneySupply; } + unsigned int emissionSpeedFactor() const { return m_emissionSpeedFactor; } + + size_t rewardBlocksWindow() const { return m_rewardBlocksWindow; } + size_t blockGrantedFullRewardZone() const { return m_blockGrantedFullRewardZone; } + size_t minerTxBlobReservedSize() const { return m_minerTxBlobReservedSize; } + + size_t numberOfDecimalPlaces() const { return m_numberOfDecimalPlaces; } + uint64_t coin() const { return m_coin; } + + uint64_t minimumFee() const { return m_mininumFee; } + uint64_t defaultDustThreshold() const { return m_defaultDustThreshold; } + + uint64_t difficultyTarget() const { return m_difficultyTarget; } + size_t difficultyWindow() const { return m_difficultyWindow; } + size_t difficultyLag() const { return m_difficultyLag; } + size_t difficultyCut() const { return m_difficultyCut; } + size_t difficultyBlocksCount() const { return m_difficultyWindow + m_difficultyLag; } + + size_t maxBlockSizeInitial() const { return m_maxBlockSizeInitial; } + uint64_t maxBlockSizeGrowthSpeedNumerator() const { return m_maxBlockSizeGrowthSpeedNumerator; } + uint64_t maxBlockSizeGrowthSpeedDenominator() const { return m_maxBlockSizeGrowthSpeedDenominator; } + + uint64_t lockedTxAllowedDeltaSeconds() const { return m_lockedTxAllowedDeltaSeconds; } + size_t lockedTxAllowedDeltaBlocks() const { return m_lockedTxAllowedDeltaBlocks; } + + uint64_t mempoolTxLiveTime() const { return m_mempoolTxLiveTime; } + uint64_t mempoolTxFromAltBlockLiveTime() const { return m_mempoolTxFromAltBlockLiveTime; } + + uint64_t upgradeHeight() const { return m_upgradeHeight; } + unsigned int upgradeVotingThreshold() const { return m_upgradeVotingThreshold; } + size_t upgradeVotingWindow() const { return m_upgradeVotingWindow; } + size_t upgradeWindow() const { return m_upgradeWindow; } + size_t minNumberVotingBlocks() const { return (m_upgradeVotingWindow * m_upgradeVotingThreshold + 99) / 100; } + uint64_t maxUpgradeDistance() const { return static_cast(m_upgradeWindow); } + uint64_t calculateUpgradeHeight(uint64_t voteCompleteHeight) const { return voteCompleteHeight + m_upgradeWindow; } + + const std::string& blocksFileName() const { return m_blocksFileName; } + const std::string& blocksCacheFileName() const { return m_blocksCacheFileName; } + const std::string& blockIndexesFileName() const { return m_blockIndexesFileName; } + const std::string& txPoolFileName() const { return m_txPoolFileName; } + + bool isTestnet() const { return m_testnet; } + + const Block& genesisBlock() const { return m_genesisBlock; } + const crypto::hash& genesisBlockHash() const { return m_genesisBlockHash; } + + bool getBlockReward(size_t medianSize, size_t currentBlockSize, uint64_t alreadyGeneratedCoins, uint64_t fee, + bool penalizeFee, uint64_t& reward, int64_t& emissionChange) const; + size_t maxBlockCumulativeSize(uint64_t height) const; + + bool constructMinerTx(size_t height, size_t medianSize, uint64_t alreadyGeneratedCoins, size_t currentBlockSize, + uint64_t fee, const AccountPublicAddress& minerAddress, Transaction& tx, + const blobdata& extraNonce = blobdata(), size_t maxOuts = 1, bool penalizeFee = false) const; + + std::string accountAddressAsString(const account_base& account) const; + bool parseAccountAddressString(const std::string& str, AccountPublicAddress& addr) const; + + std::string formatAmount(uint64_t amount) const; + bool parseAmount(const std::string& str, uint64_t& amount) const; + + difficulty_type nextDifficulty(std::vector timestamps, std::vector cumulativeDifficulties) const; + + bool checkProofOfWorkV1(crypto::cn_context& context, const Block& block, difficulty_type currentDiffic, crypto::hash& proofOfWork) const; + bool checkProofOfWorkV2(crypto::cn_context& context, const Block& block, difficulty_type currentDiffic, crypto::hash& proofOfWork) const; + bool checkProofOfWork(crypto::cn_context& context, const Block& block, difficulty_type currentDiffic, crypto::hash& proofOfWork) const; + + private: + Currency() { + } + + bool init(); + + bool generateGenesisBlock(); + + private: + uint64_t m_maxBlockHeight; + size_t m_maxBlockBlobSize; + size_t m_maxTxSize; + uint64_t m_publicAddressBase58Prefix; + size_t m_minedMoneyUnlockWindow; + + size_t m_timestampCheckWindow; + uint64_t m_blockFutureTimeLimit; + + uint64_t m_moneySupply; + unsigned int m_emissionSpeedFactor; + + size_t m_rewardBlocksWindow; + size_t m_blockGrantedFullRewardZone; + size_t m_minerTxBlobReservedSize; + + size_t m_numberOfDecimalPlaces; + uint64_t m_coin; + + uint64_t m_mininumFee; + uint64_t m_defaultDustThreshold; + + uint64_t m_difficultyTarget; + size_t m_difficultyWindow; + size_t m_difficultyLag; + size_t m_difficultyCut; + + size_t m_maxBlockSizeInitial; + uint64_t m_maxBlockSizeGrowthSpeedNumerator; + uint64_t m_maxBlockSizeGrowthSpeedDenominator; + + uint64_t m_lockedTxAllowedDeltaSeconds; + size_t m_lockedTxAllowedDeltaBlocks; + + uint64_t m_mempoolTxLiveTime; + uint64_t m_mempoolTxFromAltBlockLiveTime; + + uint64_t m_upgradeHeight; + unsigned int m_upgradeVotingThreshold; + size_t m_upgradeVotingWindow; + size_t m_upgradeWindow; + + std::string m_blocksFileName; + std::string m_blocksCacheFileName; + std::string m_blockIndexesFileName; + std::string m_txPoolFileName; + + bool m_testnet; + + Block m_genesisBlock; + crypto::hash m_genesisBlockHash; + + friend class CurrencyBuilder; + }; + + class CurrencyBuilder : boost::noncopyable { + public: + CurrencyBuilder(); + + Currency currency() { + if (!m_currency.init()) { + throw std::runtime_error("Failed to initialize currency object"); + } + return m_currency; + } + + CurrencyBuilder& maxBlockNumber(uint64_t val) { m_currency.m_maxBlockHeight = val; return *this; } + CurrencyBuilder& maxBlockBlobSize(size_t val) { m_currency.m_maxBlockBlobSize = val; return *this; } + CurrencyBuilder& maxTxSize(size_t val) { m_currency.m_maxTxSize = val; return *this; } + CurrencyBuilder& publicAddressBase58Prefix(uint64_t val) { m_currency.m_publicAddressBase58Prefix = val; return *this; } + CurrencyBuilder& minedMoneyUnlockWindow(size_t val) { m_currency.m_minedMoneyUnlockWindow = val; return *this; } + + CurrencyBuilder& timestampCheckWindow(size_t val) { m_currency.m_timestampCheckWindow = val; return *this; } + CurrencyBuilder& blockFutureTimeLimit(uint64_t val) { m_currency.m_blockFutureTimeLimit = val; return *this; } + + CurrencyBuilder& moneySupply(uint64_t val) { m_currency.m_moneySupply = val; return *this; } + CurrencyBuilder& emissionSpeedFactor(unsigned int val); + + CurrencyBuilder& rewardBlocksWindow(size_t val) { m_currency.m_rewardBlocksWindow = val; return *this; } + CurrencyBuilder& blockGrantedFullRewardZone(size_t val) { m_currency.m_blockGrantedFullRewardZone = val; return *this; } + CurrencyBuilder& minerTxBlobReservedSize(size_t val) { m_currency.m_minerTxBlobReservedSize = val; return *this; } + + CurrencyBuilder& numberOfDecimalPlaces(size_t val); + + CurrencyBuilder& mininumFee(uint64_t val) { m_currency.m_mininumFee = val; return *this; } + CurrencyBuilder& defaultDustThreshold(uint64_t val) { m_currency.m_defaultDustThreshold = val; return *this; } + + CurrencyBuilder& difficultyTarget(uint64_t val) { m_currency.m_difficultyTarget = val; return *this; } + CurrencyBuilder& difficultyWindow(size_t val); + CurrencyBuilder& difficultyLag(size_t val) { m_currency.m_difficultyLag = val; return *this; } + CurrencyBuilder& difficultyCut(size_t val) { m_currency.m_difficultyCut = val; return *this; } + + CurrencyBuilder& maxBlockSizeInitial(size_t val) { m_currency.m_maxBlockSizeInitial = val; return *this; } + CurrencyBuilder& maxBlockSizeGrowthSpeedNumerator(uint64_t val) { m_currency.m_maxBlockSizeGrowthSpeedNumerator = val; return *this; } + CurrencyBuilder& maxBlockSizeGrowthSpeedDenominator(uint64_t val) { m_currency.m_maxBlockSizeGrowthSpeedDenominator = val; return *this; } + + CurrencyBuilder& lockedTxAllowedDeltaSeconds(uint64_t val) { m_currency.m_lockedTxAllowedDeltaSeconds = val; return *this; } + CurrencyBuilder& lockedTxAllowedDeltaBlocks(size_t val) { m_currency.m_lockedTxAllowedDeltaBlocks = val; return *this; } + + CurrencyBuilder& mempoolTxLiveTime(uint64_t val) { m_currency.m_mempoolTxLiveTime = val; return *this; } + CurrencyBuilder& mempoolTxFromAltBlockLiveTime(uint64_t val) { m_currency.m_mempoolTxFromAltBlockLiveTime = val; return *this; } + + CurrencyBuilder& upgradeHeight(uint64_t val) { m_currency.m_upgradeHeight = val; return *this; } + CurrencyBuilder& upgradeVotingThreshold(unsigned int val); + CurrencyBuilder& upgradeVotingWindow(size_t val) { m_currency.m_upgradeVotingWindow = val; return *this; } + CurrencyBuilder& upgradeWindow(size_t val); + + CurrencyBuilder& blocksFileName(const std::string& val) { m_currency.m_blocksFileName = val; return *this; } + CurrencyBuilder& blocksCacheFileName(const std::string& val) { m_currency.m_blocksCacheFileName = val; return *this; } + CurrencyBuilder& blockIndexesFileName(const std::string& val) { m_currency.m_blockIndexesFileName = val; return *this; } + CurrencyBuilder& txPoolFileName(const std::string& val) { m_currency.m_txPoolFileName = val; return *this; } + + CurrencyBuilder& testnet(bool val) { m_currency.m_testnet = val; return *this; } + + private: + Currency m_currency; + }; +} diff --git a/src/cryptonote_core/ITimeProvider.cpp b/src/cryptonote_core/ITimeProvider.cpp new file mode 100644 index 00000000..52d0481a --- /dev/null +++ b/src/cryptonote_core/ITimeProvider.cpp @@ -0,0 +1,18 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "ITimeProvider.h" diff --git a/src/cryptonote_core/ITimeProvider.h b/src/cryptonote_core/ITimeProvider.h new file mode 100644 index 00000000..fa650796 --- /dev/null +++ b/src/cryptonote_core/ITimeProvider.h @@ -0,0 +1,35 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include + +namespace CryptoNote { + + struct ITimeProvider { + virtual time_t now() = 0; + virtual ~ITimeProvider() {} + }; + + struct RealTimeProvider : public ITimeProvider { + virtual time_t now() { + return time(nullptr); + } + }; + +} diff --git a/src/cryptonote_core/ITransactionValidator.h b/src/cryptonote_core/ITransactionValidator.h new file mode 100644 index 00000000..e0b974f5 --- /dev/null +++ b/src/cryptonote_core/ITransactionValidator.h @@ -0,0 +1,51 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include "cryptonote_core/cryptonote_basic.h" + +namespace CryptoNote { + + struct BlockInfo { + uint64_t height; + crypto::hash id; + + BlockInfo() { + clear(); + } + + void clear() { + height = 0; + id = cryptonote::null_hash; + } + + bool empty() const { + return id == cryptonote::null_hash; + } + }; + + class ITransactionValidator { + public: + virtual ~ITransactionValidator() {} + + virtual bool checkTransactionInputs(const cryptonote::Transaction& tx, BlockInfo& maxUsedBlock) = 0; + virtual bool checkTransactionInputs(const cryptonote::Transaction& tx, BlockInfo& maxUsedBlock, BlockInfo& lastFailed) = 0; + virtual bool haveSpentKeyImages(const cryptonote::Transaction& tx) = 0; + }; + +} diff --git a/src/cryptonote_core/SwappedVector.h b/src/cryptonote_core/SwappedVector.h index 4523b3c1..c5fcc6ad 100755 --- a/src/cryptonote_core/SwappedVector.h +++ b/src/cryptonote_core/SwappedVector.h @@ -25,12 +25,119 @@ #include #include #include -//#include -//#include #include "serialization/binary_archive.h" template class SwappedVector { public: + typedef T value_type; + + class const_iterator { + public: + typedef ptrdiff_t difference_type; + typedef std::random_access_iterator_tag iterator_category; + typedef const T* pointer; + typedef const T& reference; + typedef T value_type; + + const_iterator() { + } + + const_iterator(SwappedVector* swappedVector, std::size_t index) : m_swappedVector(swappedVector), m_index(index) { + } + + bool operator!=(const const_iterator& other) const { + return m_index != other.m_index; + } + + bool operator<(const const_iterator& other) const { + return m_index < other.m_index; + } + + bool operator<=(const const_iterator& other) const { + return m_index <= other.m_index; + } + + bool operator==(const const_iterator& other) const { + return m_index == other.m_index; + } + + bool operator>(const const_iterator& other) const { + return m_index > other.m_index; + } + + bool operator>=(const const_iterator& other) const { + return m_index >= other.m_index; + } + + const_iterator& operator++() { + ++m_index; + return *this; + } + + const_iterator operator++(int) { + const_iterator i = *this; + ++m_index; + return i; + } + + const_iterator& operator--() { + --m_index; + return *this; + } + + const_iterator operator--(int) { + const_iterator i = *this; + --m_index; + return i; + } + + const_iterator& operator+=(difference_type n) { + m_index += n; + return *this; + } + + const_iterator& operator-=(difference_type n) { + m_index -= n; + return *this; + } + + const_iterator operator+(difference_type n) const { + return const_iterator(m_swappedVector, m_index + n); + } + + friend const_iterator operator+(difference_type n, const const_iterator& i) { + return const_iterator(i.m_swappedVector, n + i.m_index); + } + + difference_type operator-(const const_iterator& other) const { + return m_index - other.m_index; + } + + const_iterator& operator-(difference_type n) const { + return const_iterator(m_swappedVector, m_index - n); + } + + const T& operator*() const { + return (*m_swappedVector)[m_index]; + } + + const T* operator->() const { + return &(*m_swappedVector)[m_index]; + } + + const T& operator[](difference_type offset) const { + return (*m_swappedVector)[m_index + offset]; + } + + std::size_t index() const { + return m_index; + } + + private: + SwappedVector* m_swappedVector; + std::size_t m_index; + }; + SwappedVector(); //SwappedVector(const SwappedVector&) = delete; ~SwappedVector(); @@ -41,6 +148,8 @@ public: bool empty() const; uint64_t size() const; + const_iterator begin(); + const_iterator end(); const T& operator[](uint64_t index); const T& front(); const T& back(); @@ -149,6 +258,14 @@ template uint64_t SwappedVector::size() const { return m_offsets.size(); } +template typename SwappedVector::const_iterator SwappedVector::begin() { + return const_iterator(this, 0); +} + +template typename SwappedVector::const_iterator SwappedVector::end() { + return const_iterator(this, m_offsets.size()); +} + template const T& SwappedVector::operator[](uint64_t index) { auto itemIter = m_items.find(index); if (itemIter != m_items.end()) { @@ -170,13 +287,6 @@ template const T& SwappedVector::operator[](uint64_t index) { m_itemsFile.seekg(m_offsets[index]); T tempItem; - //try { - //boost::archive::binary_iarchive archive(m_itemsFile); - //archive & tempItem; - //} catch (std::exception&) { - // throw std::runtime_error("SwappedVector::operator[]"); - //} - binary_archive archive(m_itemsFile); if (!do_serialize(archive, tempItem)) { throw std::runtime_error("SwappedVector::operator[]"); @@ -244,13 +354,6 @@ template void SwappedVector::push_back(const T& item) { } m_itemsFile.seekp(m_itemsFileSize); - //try { - // boost::archive::binary_oarchive archive(m_itemsFile); - // archive & item; - //} catch (std::exception&) { - // throw std::runtime_error("SwappedVector::push_back"); - //} - binary_archive archive(m_itemsFile); if (!do_serialize(archive, *const_cast(&item))) { throw std::runtime_error("SwappedVector::push_back"); diff --git a/src/cryptonote_core/UpgradeDetector.cpp b/src/cryptonote_core/UpgradeDetector.cpp new file mode 100644 index 00000000..c42dbd42 --- /dev/null +++ b/src/cryptonote_core/UpgradeDetector.cpp @@ -0,0 +1,18 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "UpgradeDetector.h" diff --git a/src/cryptonote_core/UpgradeDetector.h b/src/cryptonote_core/UpgradeDetector.h new file mode 100644 index 00000000..060fdcf0 --- /dev/null +++ b/src/cryptonote_core/UpgradeDetector.h @@ -0,0 +1,193 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include +#include +#include + +// epee +#include "include_base_utils.h" + +#include "cryptonote_core/Currency.h" +#include "cryptonote_config.h" + +namespace cryptonote { + class UpgradeDetectorBase { + public: + enum : uint64_t { + UNDEF_HEIGHT = static_cast(-1), + }; + }; + + static_assert(cryptonote::UpgradeDetectorBase::UNDEF_HEIGHT == UINT64_C(0xFFFFFFFFFFFFFFFF), "UpgradeDetectorBase::UNDEF_HEIGHT has invalid value"); + + template + class BasicUpgradeDetector : public UpgradeDetectorBase { + public: + BasicUpgradeDetector(const Currency& currency, BC& blockchain, uint8_t targetVersion) : + m_currency(currency), + m_blockchain(blockchain), + m_targetVersion(targetVersion), + m_votingCompleteHeight(UNDEF_HEIGHT) { + } + + bool init() { + if (m_currency.upgradeHeight() == UNDEF_HEIGHT) { + if (m_blockchain.empty()) { + m_votingCompleteHeight = UNDEF_HEIGHT; + + } else if (m_targetVersion - 1 == m_blockchain.back().bl.majorVersion) { + m_votingCompleteHeight = findVotingCompleteHeight(m_blockchain.size() - 1); + + } else if (m_targetVersion <= m_blockchain.back().bl.majorVersion) { + auto it = std::lower_bound(m_blockchain.begin(), m_blockchain.end(), m_targetVersion, + [](const typename BC::value_type& b, uint8_t v) { return b.bl.majorVersion < v; }); + CHECK_AND_ASSERT_MES(it != m_blockchain.end() && it->bl.majorVersion == m_targetVersion, false, + "Internal error: upgrade height isn't found"); + uint64_t upgradeHeight = it - m_blockchain.begin(); + m_votingCompleteHeight = findVotingCompleteHeight(upgradeHeight); + CHECK_AND_ASSERT_MES(m_votingCompleteHeight != UNDEF_HEIGHT, false, + "Internal error: voting complete height isn't found, upgrade height = " << upgradeHeight); + + } else { + m_votingCompleteHeight = UNDEF_HEIGHT; + } + } else if (!m_blockchain.empty()) { + if (m_blockchain.size() <= m_currency.upgradeHeight() + 1) { + CHECK_AND_ASSERT_MES(m_blockchain.back().bl.majorVersion == m_targetVersion - 1, false, + "Internal error: block at height " << (m_blockchain.size() - 1) << " has invalid version " << + static_cast(m_blockchain.back().bl.majorVersion) << ", expected " << static_cast(m_targetVersion)); + } else { + int blockVersionAtUpgradeHeight = m_blockchain[m_currency.upgradeHeight()].bl.majorVersion; + CHECK_AND_ASSERT_MES(blockVersionAtUpgradeHeight == m_targetVersion - 1, false, + "Internal error: block at height " << m_currency.upgradeHeight() << " has invalid version " << + blockVersionAtUpgradeHeight << ", expected " << static_cast(m_targetVersion - 1)); + + int blockVersionAfterUpgradeHeight = m_blockchain[m_currency.upgradeHeight() + 1].bl.majorVersion; + CHECK_AND_ASSERT_MES(blockVersionAfterUpgradeHeight == m_targetVersion, false, + "Internal error: block at height " << (m_currency.upgradeHeight() + 1) << " has invalid version " << + blockVersionAfterUpgradeHeight << ", expected " << static_cast(m_targetVersion)); + } + } + + return true; + } + + uint8_t targetVersion() const { return m_targetVersion; } + uint64_t votingCompleteHeight() const { return m_votingCompleteHeight; } + + uint64_t upgradeHeight() const { + if (m_currency.upgradeHeight() == UNDEF_HEIGHT) { + return m_votingCompleteHeight == UNDEF_HEIGHT ? UNDEF_HEIGHT : m_currency.calculateUpgradeHeight(m_votingCompleteHeight); + } else { + return m_currency.upgradeHeight(); + } + } + + void blockPushed() { + assert(!m_blockchain.empty()); + + if (m_currency.upgradeHeight() != UNDEF_HEIGHT) { + if (m_blockchain.size() <= m_currency.upgradeHeight() + 1) { + assert(m_blockchain.back().bl.majorVersion == m_targetVersion - 1); + } else { + assert(m_blockchain.back().bl.majorVersion == m_targetVersion); + } + + } else if (m_votingCompleteHeight != UNDEF_HEIGHT) { + assert(m_blockchain.size() > m_votingCompleteHeight); + + if (m_blockchain.size() <= upgradeHeight()) { + assert(m_blockchain.back().bl.majorVersion == m_targetVersion - 1); + + if (m_blockchain.size() % (60 * 60 / m_currency.difficultyTarget()) == 0) { + LOG_PRINT_GREEN("###### UPGRADE is going to happen after height " << upgradeHeight() << "!", LOG_LEVEL_2); + } + } else if (m_blockchain.size() == upgradeHeight() + 1) { + assert(m_blockchain.back().bl.majorVersion == m_targetVersion - 1); + + LOG_PRINT_GREEN("###### UPGRADE has happened! Starting from height " << (upgradeHeight() + 1) << + " blocks with major version below " << static_cast(m_targetVersion) << " will be rejected!", LOG_LEVEL_2); + } else { + assert(m_blockchain.back().bl.majorVersion == m_targetVersion); + } + + } else { + uint64_t lastBlockHeight = m_blockchain.size() - 1; + if (isVotingComplete(lastBlockHeight)) { + m_votingCompleteHeight = lastBlockHeight; + LOG_PRINT_GREEN("###### UPGRADE voting complete at height " << m_votingCompleteHeight << + "! UPGRADE is going to happen after height " << upgradeHeight() << "!", LOG_LEVEL_2); + } + } + } + + void blockPopped() { + if (m_votingCompleteHeight != UNDEF_HEIGHT) { + assert(m_currency.upgradeHeight() == UNDEF_HEIGHT); + + if (m_blockchain.size() == m_votingCompleteHeight) { + LOG_PRINT_YELLOW("###### UPGRADE after height " << upgradeHeight() << " has been cancelled!", LOG_LEVEL_2); + m_votingCompleteHeight = UNDEF_HEIGHT; + } else { + assert(m_blockchain.size() > m_votingCompleteHeight); + } + } + } + + private: + uint64_t findVotingCompleteHeight(uint64_t probableUpgradeHeight) { + assert(m_currency.upgradeHeight() == UNDEF_HEIGHT); + + uint64_t probableVotingCompleteHeight = probableUpgradeHeight > m_currency.maxUpgradeDistance() ? + probableUpgradeHeight - m_currency.maxUpgradeDistance() : 0; + for (size_t i = probableVotingCompleteHeight; i <= probableUpgradeHeight; ++i) { + if (isVotingComplete(i)) { + return i; + } + } + + return UNDEF_HEIGHT; + } + + bool isVotingComplete(uint64_t height) { + assert(m_currency.upgradeHeight() == UNDEF_HEIGHT); + assert(m_currency.upgradeVotingWindow() > 1); + assert(m_currency.upgradeVotingThreshold() > 0 && m_currency.upgradeVotingThreshold() <= 100); + + if (height < static_cast(m_currency.upgradeVotingWindow()) - 1) { + return false; + } + + unsigned int voteCounter = 0; + for (size_t i = height + 1 - m_currency.upgradeVotingWindow(); i <= height; ++i) { + const auto& b = m_blockchain[i].bl; + voteCounter += (b.majorVersion == m_targetVersion - 1) && (b.minorVersion == BLOCK_MINOR_VERSION_1) ? 1 : 0; + } + + return m_currency.upgradeVotingThreshold() * m_currency.upgradeVotingWindow() <= 100 * voteCounter; + } + + private: + const Currency& m_currency; + BC& m_blockchain; + uint8_t m_targetVersion; + uint64_t m_votingCompleteHeight; + }; +} diff --git a/src/cryptonote_core/account.cpp b/src/cryptonote_core/account.cpp index 3524c43b..c7ba6045 100644 --- a/src/cryptonote_core/account.cpp +++ b/src/cryptonote_core/account.cpp @@ -15,21 +15,18 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . +#include "account.h" + #include #include #include #include "include_base_utils.h" -#include "account.h" #include "warnings.h" -#include "crypto/crypto.h" -#include "cryptonote_core/cryptonote_basic_impl.h" -#include "cryptonote_core/cryptonote_format_utils.h" -using namespace std; DISABLE_VS_WARNINGS(4244 4345) - namespace cryptonote +namespace cryptonote { //----------------------------------------------------------------- account_base::account_base() @@ -44,8 +41,8 @@ DISABLE_VS_WARNINGS(4244 4345) //----------------------------------------------------------------- void account_base::generate() { - generate_keys(m_keys.m_account_address.m_spend_public_key, m_keys.m_spend_secret_key); - generate_keys(m_keys.m_account_address.m_view_public_key, m_keys.m_view_secret_key); + crypto::generate_keys(m_keys.m_account_address.m_spendPublicKey, m_keys.m_spend_secret_key); + crypto::generate_keys(m_keys.m_account_address.m_viewPublicKey, m_keys.m_view_secret_key); m_creation_timestamp = time(NULL); } //----------------------------------------------------------------- @@ -54,10 +51,4 @@ DISABLE_VS_WARNINGS(4244 4345) return m_keys; } //----------------------------------------------------------------- - std::string account_base::get_public_address_str() - { - //TODO: change this code into base 58 - return get_account_address_as_str(m_keys.m_account_address); - } - //----------------------------------------------------------------- } diff --git a/src/cryptonote_core/account.h b/src/cryptonote_core/account.h index 4574af58..a53ebfa3 100644 --- a/src/cryptonote_core/account.h +++ b/src/cryptonote_core/account.h @@ -19,34 +19,24 @@ #include "cryptonote_core/cryptonote_basic.h" #include "crypto/crypto.h" -#include "serialization/keyvalue_serialization.h" -namespace cryptonote -{ +namespace cryptonote { + template struct AccountBaseSerializer; - struct account_keys - { - account_public_address m_account_address; + struct account_keys { + AccountPublicAddress m_account_address; crypto::secret_key m_spend_secret_key; crypto::secret_key m_view_secret_key; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(m_account_address) - KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_spend_secret_key) - KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_view_secret_key) - END_KV_SERIALIZE_MAP() }; /************************************************************************/ /* */ /************************************************************************/ - class account_base - { + class account_base { public: account_base(); void generate(); const account_keys& get_keys() const; - std::string get_public_address_str(); uint64_t get_createtime() const { return m_creation_timestamp; } void set_createtime(uint64_t val) { m_creation_timestamp = val; } @@ -55,20 +45,17 @@ namespace cryptonote bool store(const std::string& file_path); template - inline void serialize(t_archive &a, const unsigned int /*ver*/) - { + inline void serialize(t_archive &a, const unsigned int /*ver*/) { a & m_keys; a & m_creation_timestamp; } - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(m_keys) - KV_SERIALIZE(m_creation_timestamp) - END_KV_SERIALIZE_MAP() - private: void set_null(); account_keys m_keys; uint64_t m_creation_timestamp; + + friend struct AccountBaseSerializer; + friend struct AccountBaseSerializer; }; } diff --git a/src/cryptonote_core/account_boost_serialization.h b/src/cryptonote_core/account_boost_serialization.h index ee21d718..e391b39b 100644 --- a/src/cryptonote_core/account_boost_serialization.h +++ b/src/cryptonote_core/account_boost_serialization.h @@ -34,10 +34,10 @@ namespace boost } template - inline void serialize(Archive &a, cryptonote::account_public_address &x, const boost::serialization::version_type ver) + inline void serialize(Archive &a, cryptonote::AccountPublicAddress &x, const boost::serialization::version_type ver) { - a & x.m_spend_public_key; - a & x.m_view_public_key; + a & x.m_spendPublicKey; + a & x.m_viewPublicKey; } } diff --git a/src/cryptonote_core/blockchain_storage.cpp b/src/cryptonote_core/blockchain_storage.cpp index 5ea4d098..9b578959 100644 --- a/src/cryptonote_core/blockchain_storage.cpp +++ b/src/cryptonote_core/blockchain_storage.cpp @@ -20,15 +20,21 @@ #include #include + #include #include +// epee +#include "file_io_utils.h" +#include "misc_language.h" +#include "profile_tools.h" +#include "time_helper.h" + +#include "common/boost_serialization_helper.h" #include "cryptonote_format_utils.h" #include "cryptonote_boost_serialization.h" +#include "rpc/core_rpc_server_commands_defs.h" -#include "profile_tools.h" -#include "file_io_utils.h" -#include "common/boost_serialization_helper.h" //namespace { // std::string hashHex(const crypto::hash& hash) { @@ -70,7 +76,7 @@ DISABLE_VS_WARNINGS(4267) namespace cryptonote { struct transaction_chain_entry { - transaction tx; + Transaction tx; uint64_t m_keeper_block_height; size_t m_blob_size; std::vector m_global_output_indexes; @@ -79,7 +85,7 @@ namespace cryptonote { }; struct block_extended_info { - block bl; + Block bl; uint64_t height; size_t block_cumulative_size; difficulty_type cumulative_difficulty; @@ -104,11 +110,11 @@ namespace cryptonote { } } -template void cryptonote::blockchain_storage::Transaction::serialize(Archive& archive, unsigned int version) { +template void cryptonote::blockchain_storage::TransactionEntry::serialize(Archive& archive, unsigned int version) { archive & tx; } -template void cryptonote::blockchain_storage::Block::serialize(Archive& archive, unsigned int version) { +template void cryptonote::blockchain_storage::BlockEntry::serialize(Archive& archive, unsigned int version) { archive & bl; archive & height; archive & block_cumulative_size; @@ -122,6 +128,12 @@ template void cryptonote::blockchain_storage::TransactionIndex::s archive & transaction; } +template void cryptonote::blockchain_storage::MultisignatureOutputUsage::serialize(Archive& archive, unsigned int version) { + archive & transactionIndex; + archive & outputIndex; + archive & isUsed; +} + namespace cryptonote { #define CURRENT_BLOCKCHAIN_STORAGE_ARCHIVE_VER 13 @@ -172,22 +184,22 @@ namespace cryptonote { "transactions: " << transactions.size() << ENDL << "current_block_cumul_sz_limit: " << current_block_cumul_sz_limit); - Block block; - Transaction transaction; + BlockEntry block; + TransactionEntry transaction; for (uint32_t b = 0; b < blocks.size(); ++b) { block.bl = blocks[b].bl; block.height = b; block.block_cumulative_size = blocks[b].block_cumulative_size; block.cumulative_difficulty = blocks[b].cumulative_difficulty; block.already_generated_coins = blocks[b].already_generated_coins; - block.transactions.resize(1 + blocks[b].bl.tx_hashes.size()); - block.transactions[0].tx = blocks[b].bl.miner_tx; + block.transactions.resize(1 + blocks[b].bl.txHashes.size()); + block.transactions[0].tx = blocks[b].bl.minerTx; TransactionIndex transactionIndex = { b, 0 }; - pushTransaction(block, get_transaction_hash(blocks[b].bl.miner_tx), transactionIndex); - for (uint32_t t = 0; t < blocks[b].bl.tx_hashes.size(); ++t) { - block.transactions[1 + t].tx = transactions[blocks[b].bl.tx_hashes[t]].tx; + pushTransaction(block, get_transaction_hash(blocks[b].bl.minerTx), transactionIndex); + for (uint32_t t = 0; t < blocks[b].bl.txHashes.size(); ++t) { + block.transactions[1 + t].tx = transactions[blocks[b].bl.txHashes[t]].tx; transactionIndex.transaction = 1 + t; - pushTransaction(block, blocks[b].bl.tx_hashes[t], transactionIndex); + pushTransaction(block, blocks[b].bl.txHashes[t], transactionIndex); } pushBlock(block); @@ -202,6 +214,119 @@ namespace cryptonote { BOOST_CLASS_VERSION(cryptonote::blockchain_storage, CURRENT_BLOCKCHAIN_STORAGE_ARCHIVE_VER) +namespace cryptonote +{ + +#define CURRENT_BLOCKCACHE_STORAGE_ARCHIVE_VER 1 + + class BlockCacheSerializer { + + public: + BlockCacheSerializer(blockchain_storage& bs, const crypto::hash lastBlockHash) : + m_bs(bs), m_lastBlockHash(lastBlockHash), m_loaded(false) {} + + template void serialize(Archive& ar, unsigned int version) { + + // ignore old versions, do rebuild + if (version < CURRENT_BLOCKCACHE_STORAGE_ARCHIVE_VER) + return; + + if (Archive::is_loading::value) { + + crypto::hash blockHash; + ar & blockHash; + + if (blockHash != m_lastBlockHash) { + return; + } + + } else { + ar & m_lastBlockHash; + } + + ar & m_bs.m_blockIndex; + ar & m_bs.m_transactionMap; + ar & m_bs.m_spent_keys; + ar & m_bs.m_outputs; + ar & m_bs.m_multisignatureOutputs; + + m_loaded = true; + } + + bool loaded() const { + return m_loaded; + } + + private: + + bool m_loaded; + blockchain_storage& m_bs; + crypto::hash m_lastBlockHash; + }; +} + +BOOST_CLASS_VERSION(cryptonote::BlockCacheSerializer, CURRENT_BLOCKCACHE_STORAGE_ARCHIVE_VER) + + +blockchain_storage::blockchain_storage(const Currency& currency, tx_memory_pool& tx_pool): + m_currency(currency), + m_tx_pool(tx_pool), + m_current_block_cumul_sz_limit(currency.blockGrantedFullRewardZone() * 2), + m_is_in_checkpoint_zone(false), + m_is_blockchain_storing(false), + m_upgradeDetector(currency, m_blocks, BLOCK_MAJOR_VERSION_2) { + m_outputs.set_deleted_key(0); + + crypto::key_image nullImage = AUTO_VAL_INIT(nullImage); + m_spent_keys.set_deleted_key(nullImage); +} + +bool blockchain_storage::checkTransactionInputs(const cryptonote::Transaction& tx, BlockInfo& maxUsedBlock) { + return check_tx_inputs(tx, maxUsedBlock.height, maxUsedBlock.id); +} + +bool blockchain_storage::checkTransactionInputs(const cryptonote::Transaction& tx, BlockInfo& maxUsedBlock, BlockInfo& lastFailed) { + + BlockInfo tail; + + //not the best implementation at this time, sorry :( + //check is ring_signature already checked ? + if (maxUsedBlock.empty()) { + //not checked, lets try to check + if (!lastFailed.empty() && get_current_blockchain_height() > lastFailed.height && get_block_id_by_height(lastFailed.height) == lastFailed.id) { + return false; //we already sure that this tx is broken for this height + } + + if (!check_tx_inputs(tx, maxUsedBlock.height, maxUsedBlock.id, &tail)) { + lastFailed = tail; + return false; + } + } + else { + if (maxUsedBlock.height >= get_current_blockchain_height()) { + return false; + } + + if (get_block_id_by_height(maxUsedBlock.height) != maxUsedBlock.id) { + //if we already failed on this height and id, skip actual ring signature check + if (lastFailed.id == get_block_id_by_height(lastFailed.height)) { + return false; + } + + //check ring signature again, it is possible (with very small chance) that this transaction become again valid + if (!check_tx_inputs(tx, maxUsedBlock.height, maxUsedBlock.id, &tail)) { + lastFailed = tail; + return false; + } + } + } + + return true; +} + +bool blockchain_storage::haveSpentKeyImages(const cryptonote::Transaction& tx) { + return this->have_tx_keyimges_as_spent(tx); +} bool blockchain_storage::have_tx(const crypto::hash &id) { CRITICAL_REGION_LOCAL(m_blockchain_lock); @@ -227,7 +352,7 @@ bool blockchain_storage::init(const std::string& config_folder, bool load_existi m_config_folder = config_folder; - if (!m_blocks.open(appendPath(config_folder, "blocks.dat"), appendPath(config_folder, "blockindexes.dat"), 1024)) { + if (!m_blocks.open(appendPath(config_folder, m_currency.blocksFileName()), appendPath(config_folder, m_currency.blockIndexesFileName()), 1024)) { return false; } @@ -235,42 +360,34 @@ bool blockchain_storage::init(const std::string& config_folder, bool load_existi LOG_PRINT_L0("Loading blockchain..."); if (m_blocks.empty()) { - const std::string filename = appendPath(m_config_folder, CRYPTONOTE_BLOCKCHAINDATA_FILENAME); + const std::string filename = appendPath(m_config_folder, cryptonote::parameters::CRYPTONOTE_BLOCKCHAINDATA_FILENAME); if (!tools::unserialize_obj_from_file(*this, filename)) { LOG_PRINT_L0("Can't load blockchain storage from file."); } } else { - bool rebuild = true; - try { - std::ifstream file(appendPath(config_folder, "blockscache.dat"), std::ios::binary); - boost::archive::binary_iarchive archive(file); - crypto::hash lastBlockHash; - archive & lastBlockHash; - if (lastBlockHash == get_block_hash(m_blocks.back().bl)) { - archive & m_blockMap; - archive & m_transactionMap; - archive & m_spent_keys; - archive & m_outputs; - rebuild = false; - } - } catch (std::exception&) { - } + BlockCacheSerializer loader(*this, get_block_hash(m_blocks.back().bl)); + tools::unserialize_obj_from_file(loader, appendPath(config_folder, m_currency.blocksCacheFileName())); - if (rebuild) { + if (!loader.loaded()) { LOG_PRINT_L0("No actual blockchain cache found, rebuilding internal structures..."); std::chrono::steady_clock::time_point timePoint = std::chrono::steady_clock::now(); + m_blockIndex.clear(); + m_transactionMap.clear(); + m_spent_keys.clear(); + m_outputs.clear(); + m_multisignatureOutputs.clear(); for (uint32_t b = 0; b < m_blocks.size(); ++b) { - const Block& block = m_blocks[b]; + const BlockEntry& block = m_blocks[b]; crypto::hash blockHash = get_block_hash(block.bl); - m_blockMap.insert(std::make_pair(blockHash, b)); + m_blockIndex.push(blockHash); for (uint16_t t = 0; t < block.transactions.size(); ++t) { - const Transaction& transaction = block.transactions[t]; + const TransactionEntry& transaction = block.transactions[t]; crypto::hash transactionHash = get_transaction_hash(transaction.tx); TransactionIndex transactionIndex = { b, t }; m_transactionMap.insert(std::make_pair(transactionHash, transactionIndex)); for (auto& i : transaction.tx.vin) { - if (i.type() == typeid(txin_to_key)) { - m_spent_keys.insert(::boost::get(i).k_image); + if (i.type() == typeid(TransactionInputToKey)) { + m_spent_keys.insert(::boost::get(i).keyImage); } } @@ -290,110 +407,50 @@ bool blockchain_storage::init(const std::string& config_folder, bool load_existi if (m_blocks.empty()) { LOG_PRINT_L0("Blockchain not loaded, generating genesis block."); - block bl = ::boost::value_initialized(); block_verification_context bvc = boost::value_initialized(); - generate_genesis_block(bl); - add_new_block(bl, bvc); + add_new_block(m_currency.genesisBlock(), bvc); CHECK_AND_ASSERT_MES(!bvc.m_verifivation_failed, false, "Failed to add genesis block to blockchain"); + } else { + crypto::hash firstBlockHash = get_block_hash(m_blocks[0].bl); + CHECK_AND_ASSERT_MES(firstBlockHash == m_currency.genesisBlockHash(), false, + "Failed to init: genesis block mismatch. Probably you set --testnet flag with data dir with non-test blockchain or another network."); + } + + if (!m_upgradeDetector.init()) { + LOG_ERROR("Failed to initialize upgrade detector"); + return false; } uint64_t timestamp_diff = time(NULL) - m_blocks.back().bl.timestamp; - if (!m_blocks.back().bl.timestamp) + if (!m_blocks.back().bl.timestamp) { timestamp_diff = time(NULL) - 1341378000; + } + LOG_PRINT_GREEN("Blockchain initialized. last block: " << m_blocks.size() - 1 << ", " << epee::misc_utils::get_time_interval_string(timestamp_diff) << " time ago, current difficulty: " << get_difficulty_for_next_block(), LOG_LEVEL_0); return true; } -bool blockchain_storage::store_blockchain() { - try { - std::ofstream file(appendPath(m_config_folder, "blockscache.dat"), std::ios::binary); - boost::archive::binary_oarchive archive(file); - crypto::hash lastBlockHash = get_block_hash(m_blocks.back().bl); - archive & lastBlockHash; - archive & m_blockMap; - archive & m_transactionMap; - archive & m_spent_keys; - archive & m_outputs; - } catch (std::exception& e) { - LOG_ERROR("Failed to save blockchain, " << e.what()); - } +bool blockchain_storage::storeCache() { + CRITICAL_REGION_LOCAL(m_blockchain_lock); - //{ - // std::ofstream file(appendPath(m_config_folder, "blockscache2.dat"), std::ios::binary); - - // crypto::hash lastBlockHash = get_block_hash(m_blocks.back().bl); - // file.write(reinterpret_cast(&lastBlockHash), sizeof(lastBlockHash)); - - // uint32_t blockMapSize = m_blockMap.size(); - // file.write(reinterpret_cast(&blockMapSize), sizeof(blockMapSize)); - // for (auto& i : m_blockMap) { - // crypto::hash blockHash = i.first; - // file.write(reinterpret_cast(&blockHash), sizeof(blockHash)); - - // uint32_t blockIndex = i.second; - // file.write(reinterpret_cast(&blockIndex), sizeof(blockIndex)); - // } - - // uint32_t transactionMapSize = m_transactionMap.size(); - // file.write(reinterpret_cast(&transactionMapSize), sizeof(transactionMapSize)); - // for (auto& i : m_transactionMap) { - // crypto::hash transactionHash = i.first; - // file.write(reinterpret_cast(&transactionHash), sizeof(transactionHash)); - - // uint32_t blockIndex = i.second.block; - // file.write(reinterpret_cast(&blockIndex), sizeof(blockIndex)); - - // uint32_t transactionIndex = i.second.transaction; - // file.write(reinterpret_cast(&transactionIndex), sizeof(transactionIndex)); - // } - - // uint32_t spentKeysSize = m_spent_keys.size(); - // file.write(reinterpret_cast(&spentKeysSize), sizeof(spentKeysSize)); - // for (auto& i : m_spent_keys) { - // crypto::key_image key = i; - // file.write(reinterpret_cast(&key), sizeof(key)); - // } - - // uint32_t outputsSize = m_outputs.size(); - // file.write(reinterpret_cast(&outputsSize), sizeof(outputsSize)); - // for (auto& i : m_outputs) { - // uint32_t indexesSize = i.second.size(); - // file.write(reinterpret_cast(&indexesSize), sizeof(indexesSize)); - // for (auto& j : i.second) { - // uint32_t blockIndex = j.first.block; - // file.write(reinterpret_cast(&blockIndex), sizeof(blockIndex)); - - // uint32_t transactionIndex = j.first.transaction; - // file.write(reinterpret_cast(&transactionIndex), sizeof(transactionIndex)); - - // uint32_t outputIndex = j.second; - // file.write(reinterpret_cast(&outputIndex), sizeof(outputIndex)); - // } - // } - //} - - { - //std::ofstream file(appendPath(m_config_folder, "blockscache3.dat"), std::ios::binary); - //binary_archive archive(file); - //crypto::hash lastBlockHash = get_block_hash(m_blocks.back().bl); - //do_serialize(archive, lastBlockHash); - //do_serialize(archive, m_blockMap); - //do_serialize(archive, m_transactionMap); - //do_serialize(archive, m_spent_keys); - //do_serialize(archive, m_outputs); + BlockCacheSerializer ser(*this, get_tail_id()); + if (!tools::serialize_obj_to_file(ser, appendPath(m_config_folder, m_currency.blocksCacheFileName()))) { + LOG_ERROR("Failed to save blockchain cache"); + return false; } return true; } bool blockchain_storage::deinit() { - return store_blockchain(); + storeCache(); + return true; } -bool blockchain_storage::reset_and_set_genesis_block(const block& b) { +bool blockchain_storage::reset_and_set_genesis_block(const Block& b) { CRITICAL_REGION_LOCAL(m_blockchain_lock); m_blocks.clear(); - m_blockMap.clear(); + m_blockIndex.clear(); m_transactionMap.clear(); m_spent_keys.clear(); @@ -413,56 +470,26 @@ crypto::hash blockchain_storage::get_tail_id(uint64_t& height) { crypto::hash blockchain_storage::get_tail_id() { CRITICAL_REGION_LOCAL(m_blockchain_lock); - crypto::hash id = null_hash; - if (m_blocks.size()) { - get_block_hash(m_blocks.back().bl, id); - } - - return id; + return m_blockIndex.getTailId(); } bool blockchain_storage::get_short_chain_history(std::list& ids) { CRITICAL_REGION_LOCAL(m_blockchain_lock); - size_t i = 0; - size_t current_multiplier = 1; - size_t sz = m_blocks.size(); - if (!sz) - return true; - size_t current_back_offset = 1; - bool genesis_included = false; - while (current_back_offset < sz) - { - ids.push_back(get_block_hash(m_blocks[sz - current_back_offset].bl)); - if (sz - current_back_offset == 0) - genesis_included = true; - if (i < 10) - { - ++current_back_offset; - } else - { - current_back_offset += current_multiplier *= 2; - } - ++i; - } - if (!genesis_included) - ids.push_back(get_block_hash(m_blocks[0].bl)); - - return true; + return m_blockIndex.getShortChainHistory(ids); } crypto::hash blockchain_storage::get_block_id_by_height(uint64_t height) { CRITICAL_REGION_LOCAL(m_blockchain_lock); - if (height >= m_blocks.size()) - return null_hash; - - return get_block_hash(m_blocks[height].bl); + return m_blockIndex.getBlockId(height); } -bool blockchain_storage::get_block_by_hash(const crypto::hash& blockHash, block& b) { +bool blockchain_storage::get_block_by_hash(const crypto::hash& blockHash, Block& b) { CRITICAL_REGION_LOCAL(m_blockchain_lock); - auto blockIndexByHashIterator = m_blockMap.find(blockHash); - if (blockIndexByHashIterator != m_blockMap.end()) { - b = m_blocks[blockIndexByHashIterator->second].bl; + + uint64_t height = 0; + + if (m_blockIndex.getBlockHeight(blockHash, height)) { + b = m_blocks[height].bl; return true; } @@ -479,7 +506,7 @@ difficulty_type blockchain_storage::get_difficulty_for_next_block() { CRITICAL_REGION_LOCAL(m_blockchain_lock); std::vector timestamps; std::vector commulative_difficulties; - size_t offset = m_blocks.size() - std::min(m_blocks.size(), static_cast(DIFFICULTY_BLOCKS_COUNT)); + size_t offset = m_blocks.size() - std::min(m_blocks.size(), static_cast(m_currency.difficultyBlocksCount())); if (offset == 0) { ++offset; } @@ -489,10 +516,23 @@ difficulty_type blockchain_storage::get_difficulty_for_next_block() { commulative_difficulties.push_back(m_blocks[offset].cumulative_difficulty); } - return next_difficulty(timestamps, commulative_difficulties); + return m_currency.nextDifficulty(timestamps, commulative_difficulties); } -bool blockchain_storage::rollback_blockchain_switching(std::list& original_chain, size_t rollback_height) { +uint64_t blockchain_storage::getCoinsInCirculation() { + CRITICAL_REGION_LOCAL(m_blockchain_lock); + if (m_blocks.empty()) { + return 0; + } else { + return m_blocks.back().already_generated_coins; + } +} + +uint8_t blockchain_storage::get_block_major_version_for_height(uint64_t height) const { + return height > m_upgradeDetector.upgradeHeight() ? m_upgradeDetector.targetVersion() : BLOCK_MAJOR_VERSION_1; +} + +bool blockchain_storage::rollback_blockchain_switching(std::list& original_chain, size_t rollback_height) { CRITICAL_REGION_LOCAL(m_blockchain_lock); //remove failed subchain for (size_t i = m_blocks.size() - 1; i >= rollback_height; i--) @@ -521,9 +561,9 @@ bool blockchain_storage::switch_to_alternative_blockchain(std::list split_height, false, "switch_to_alternative_blockchain: blockchain size is lower than split height"); //disconnecting old chain - std::list disconnected_chain; + std::list disconnected_chain; for (size_t i = m_blocks.size() - 1; i >= split_height; i--) { - block b = m_blocks[i].bl; + Block b = m_blocks[i].bl; popBlock(get_block_hash(b)); //CHECK_AND_ASSERT_MES(r, false, "failed to remove block on chain switching"); disconnected_chain.push_front(b); @@ -573,13 +613,13 @@ bool blockchain_storage::switch_to_alternative_blockchain(std::list& alt_chain, Block& bei) { +difficulty_type blockchain_storage::get_next_difficulty_for_alternative_chain(const std::list& alt_chain, BlockEntry& bei) { std::vector timestamps; std::vector commulative_difficulties; - if (alt_chain.size() < DIFFICULTY_BLOCKS_COUNT) { + if (alt_chain.size() < m_currency.difficultyBlocksCount()) { CRITICAL_REGION_LOCAL(m_blockchain_lock); size_t main_chain_stop_offset = alt_chain.size() ? alt_chain.front()->second.height : bei.height; - size_t main_chain_count = DIFFICULTY_BLOCKS_COUNT - std::min(static_cast(DIFFICULTY_BLOCKS_COUNT), alt_chain.size()); + size_t main_chain_count = m_currency.difficultyBlocksCount() - std::min(m_currency.difficultyBlocksCount(), alt_chain.size()); main_chain_count = std::min(main_chain_count, main_chain_stop_offset); size_t main_chain_start_offset = main_chain_stop_offset - main_chain_count; @@ -590,43 +630,46 @@ difficulty_type blockchain_storage::get_next_difficulty_for_alternative_chain(co commulative_difficulties.push_back(m_blocks[main_chain_start_offset].cumulative_difficulty); } - CHECK_AND_ASSERT_MES((alt_chain.size() + timestamps.size()) <= DIFFICULTY_BLOCKS_COUNT, false, "Internal error, alt_chain.size()[" << alt_chain.size() - << "] + vtimestampsec.size()[" << timestamps.size() << "] NOT <= DIFFICULTY_WINDOW[]" << DIFFICULTY_BLOCKS_COUNT); + CHECK_AND_ASSERT_MES((alt_chain.size() + timestamps.size()) <= m_currency.difficultyBlocksCount(), false, + "Internal error, alt_chain.size()[" << alt_chain.size() << "] + timestamps.size()[" << timestamps.size() << + "] NOT <= m_currency.difficultyBlocksCount()[" << m_currency.difficultyBlocksCount() << ']'); for (auto it : alt_chain) { timestamps.push_back(it->second.bl.timestamp); commulative_difficulties.push_back(it->second.cumulative_difficulty); } } else { - timestamps.resize(std::min(alt_chain.size(), static_cast(DIFFICULTY_BLOCKS_COUNT))); - commulative_difficulties.resize(std::min(alt_chain.size(), static_cast(DIFFICULTY_BLOCKS_COUNT))); + timestamps.resize(std::min(alt_chain.size(), m_currency.difficultyBlocksCount())); + commulative_difficulties.resize(std::min(alt_chain.size(), m_currency.difficultyBlocksCount())); size_t count = 0; size_t max_i = timestamps.size() - 1; BOOST_REVERSE_FOREACH(auto it, alt_chain) { timestamps[max_i - count] = it->second.bl.timestamp; commulative_difficulties[max_i - count] = it->second.cumulative_difficulty; count++; - if (count >= DIFFICULTY_BLOCKS_COUNT) { + if (count >= m_currency.difficultyBlocksCount()) { break; } } } - return next_difficulty(timestamps, commulative_difficulties); + return m_currency.nextDifficulty(timestamps, commulative_difficulties); } -bool blockchain_storage::prevalidate_miner_transaction(const block& b, uint64_t height) { - CHECK_AND_ASSERT_MES(b.miner_tx.vin.size() == 1, false, "coinbase transaction in the block has no inputs"); - CHECK_AND_ASSERT_MES(b.miner_tx.vin[0].type() == typeid(txin_gen), false, "coinbase transaction in the block has the wrong type"); - if (boost::get(b.miner_tx.vin[0]).height != height) { - LOG_PRINT_RED_L0("The miner transaction in block has invalid height: " << boost::get(b.miner_tx.vin[0]).height << ", expected: " << height); +bool blockchain_storage::prevalidate_miner_transaction(const Block& b, uint64_t height) { + CHECK_AND_ASSERT_MES(b.minerTx.vin.size() == 1, false, "coinbase transaction in the block has no inputs"); + CHECK_AND_ASSERT_MES(b.minerTx.vin[0].type() == typeid(TransactionInputGenerate), false, + "coinbase transaction in the block has the wrong type"); + if (boost::get(b.minerTx.vin[0]).height != height) { + LOG_PRINT_RED_L0("The miner transaction in block has invalid height: " << + boost::get(b.minerTx.vin[0]).height << ", expected: " << height); return false; } - CHECK_AND_ASSERT_MES(b.miner_tx.unlock_time == height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, + CHECK_AND_ASSERT_MES(b.minerTx.unlockTime == height + m_currency.minedMoneyUnlockWindow(), false, - "coinbase transaction transaction have wrong unlock time=" << b.miner_tx.unlock_time << ", expected " << height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW); + "coinbase transaction transaction have wrong unlock time=" << b.minerTx.unlockTime << ", expected " << height + m_currency.minedMoneyUnlockWindow()); - if (!check_outs_overflow(b.miner_tx)) { + if (!check_outs_overflow(b.minerTx)) { LOG_PRINT_RED_L0("miner transaction have money overflow in block " << get_block_hash(b)); return false; } @@ -634,27 +677,31 @@ bool blockchain_storage::prevalidate_miner_transaction(const block& b, uint64_t return true; } -bool blockchain_storage::validate_miner_transaction(const block& b, size_t cumulative_block_size, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins) { - uint64_t money_in_use = 0; - for (auto& o : b.miner_tx.vout) { - money_in_use += o.amount; +bool blockchain_storage::validate_miner_transaction(const Block& b, uint64_t height, size_t cumulativeBlockSize, + uint64_t alreadyGeneratedCoins, uint64_t fee, + uint64_t& reward, int64_t& emissionChange) { + uint64_t minerReward = 0; + for (auto& o : b.minerTx.vout) { + minerReward += o.amount; } - std::vector last_blocks_sizes; - get_last_n_blocks_sizes(last_blocks_sizes, CRYPTONOTE_REWARD_BLOCKS_WINDOW); - if (!get_block_reward(epee::misc_utils::median(last_blocks_sizes), cumulative_block_size, already_generated_coins, base_reward)) { - LOG_PRINT_L0("block size " << cumulative_block_size << " is bigger than allowed for this blockchain"); + std::vector lastBlocksSizes; + get_last_n_blocks_sizes(lastBlocksSizes, m_currency.rewardBlocksWindow()); + size_t blocksSizeMedian = epee::misc_utils::median(lastBlocksSizes); + + bool penalizeFee = get_block_major_version_for_height(height) > BLOCK_MAJOR_VERSION_1; + if (!m_currency.getBlockReward(blocksSizeMedian, cumulativeBlockSize, alreadyGeneratedCoins, fee, penalizeFee, reward, emissionChange)) { + LOG_PRINT_L0("block size " << cumulativeBlockSize << " is bigger than allowed for this blockchain"); return false; } - if (base_reward + fee < money_in_use) { - LOG_ERROR("coinbase transaction spend too much money (" << print_money(money_in_use) << "). Block reward is " << print_money(base_reward + fee) << "(" << print_money(base_reward) << "+" << print_money(fee) << ")"); + if (minerReward > reward) { + LOG_ERROR("Coinbase transaction spend too much money: " << m_currency.formatAmount(minerReward) << + ", block reward is " << m_currency.formatAmount(reward)); return false; - } - - if (base_reward + fee != money_in_use) { - LOG_ERROR("coinbase transaction doesn't use full amount of block reward: spent: " - << print_money(money_in_use) << ", block reward " << print_money(base_reward + fee) << "(" << print_money(base_reward) << "+" << print_money(fee) << ")"); + } else if (minerReward < reward) { + LOG_ERROR("Coinbase transaction doesn't use full amount of block reward: spent " << + m_currency.formatAmount(minerReward) << ", block reward is " << m_currency.formatAmount(reward)); return false; } @@ -685,18 +732,33 @@ uint64_t blockchain_storage::get_current_comulative_blocksize_limit() { return m_current_block_cumul_sz_limit; } -bool blockchain_storage::create_block_template(block& b, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce) { +bool blockchain_storage::create_block_template(Block& b, const AccountPublicAddress& miner_address, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce) { size_t median_size; uint64_t already_generated_coins; CRITICAL_REGION_BEGIN(m_blockchain_lock); - b.major_version = CURRENT_BLOCK_MAJOR_VERSION; - b.minor_version = CURRENT_BLOCK_MINOR_VERSION; - b.prev_id = get_tail_id(); - b.timestamp = time(NULL); height = m_blocks.size(); diffic = get_difficulty_for_next_block(); - CHECK_AND_ASSERT_MES(diffic, false, "difficulty owverhead."); + CHECK_AND_ASSERT_MES(diffic, false, "difficulty overhead."); + + b = boost::value_initialized(); + b.majorVersion = get_block_major_version_for_height(height); + + if (BLOCK_MAJOR_VERSION_1 == b.majorVersion) { + b.minorVersion = BLOCK_MINOR_VERSION_1; + } else if (BLOCK_MAJOR_VERSION_2 == b.majorVersion) { + b.minorVersion = BLOCK_MINOR_VERSION_0; + + b.parentBlock.majorVersion = BLOCK_MAJOR_VERSION_1; + b.parentBlock.majorVersion = BLOCK_MINOR_VERSION_0; + b.parentBlock.numberOfTransactions = 1; + tx_extra_merge_mining_tag mm_tag = AUTO_VAL_INIT(mm_tag); + bool r = append_mm_tag_to_extra(b.parentBlock.minerTx.extra, mm_tag); + CHECK_AND_ASSERT_MES(r, false, "Failed to append merge mining tag to extra of the parent block miner transaction"); + } + + b.prevId = get_tail_id(); + b.timestamp = time(NULL); median_size = m_current_block_cumul_sz_limit / 2; already_generated_coins = m_blocks.back().already_generated_coins; @@ -705,7 +767,8 @@ bool blockchain_storage::create_block_template(block& b, const account_public_ad size_t txs_size; uint64_t fee; - if (!m_tx_pool.fill_block_template(b, median_size, already_generated_coins, txs_size, fee)) { + if (!m_tx_pool.fill_block_template(b, median_size, m_currency.maxBlockCumulativeSize(height), already_generated_coins, + txs_size, fee)) { return false; } @@ -713,7 +776,7 @@ bool blockchain_storage::create_block_template(block& b, const account_public_ad size_t real_txs_size = 0; uint64_t real_fee = 0; CRITICAL_REGION_BEGIN(m_tx_pool.m_transactions_lock); - BOOST_FOREACH(crypto::hash &cur_hash, b.tx_hashes) { + for (crypto::hash &cur_hash : b.txHashes) { auto cur_res = m_tx_pool.m_transactions.find(cur_hash); if (cur_res == m_tx_pool.m_transactions.end()) { LOG_ERROR("Creating block template: error: transaction not found"); @@ -751,18 +814,19 @@ bool blockchain_storage::create_block_template(block& b, const account_public_ad block size, so first miner transaction generated with fake amount of money, and with phase we know think we know expected block size */ //make blocks coin-base tx looks close to real coinbase tx to get truthful blob size - bool r = construct_miner_tx(height, median_size, already_generated_coins, txs_size, fee, miner_address, b.miner_tx, ex_nonce, 11); + bool penalizeFee = b.majorVersion > BLOCK_MAJOR_VERSION_1; + bool r = m_currency.constructMinerTx(height, median_size, already_generated_coins, txs_size, fee, miner_address, b.minerTx, ex_nonce, 11, penalizeFee); CHECK_AND_ASSERT_MES(r, false, "Failed to construc miner tx, first chance"); - size_t cumulative_size = txs_size + get_object_blobsize(b.miner_tx); + size_t cumulative_size = txs_size + get_object_blobsize(b.minerTx); #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) - LOG_PRINT_L1("Creating block template: miner tx size " << get_object_blobsize(b.miner_tx) << + LOG_PRINT_L1("Creating block template: miner tx size " << get_object_blobsize(b.minerTx) << ", cumulative size " << cumulative_size); #endif for (size_t try_count = 0; try_count != 10; ++try_count) { - r = construct_miner_tx(height, median_size, already_generated_coins, cumulative_size, fee, miner_address, b.miner_tx, ex_nonce, 11); + r = m_currency.constructMinerTx(height, median_size, already_generated_coins, cumulative_size, fee, miner_address, b.minerTx, ex_nonce, 11, penalizeFee); CHECK_AND_ASSERT_MES(r, false, "Failed to construc miner tx, second chance"); - size_t coinbase_blob_size = get_object_blobsize(b.miner_tx); + size_t coinbase_blob_size = get_object_blobsize(b.minerTx); if (coinbase_blob_size > cumulative_size - txs_size) { cumulative_size = txs_size + coinbase_blob_size; #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) @@ -779,21 +843,21 @@ bool blockchain_storage::create_block_template(block& b, const account_public_ad ", cumulative size " << txs_size + coinbase_blob_size << " is less then before, adding " << delta << " zero bytes"); #endif - b.miner_tx.extra.insert(b.miner_tx.extra.end(), delta, 0); + b.minerTx.extra.insert(b.minerTx.extra.end(), delta, 0); //here could be 1 byte difference, because of extra field counter is varint, and it can become from 1-byte len to 2-bytes len. - if (cumulative_size != txs_size + get_object_blobsize(b.miner_tx)) { - CHECK_AND_ASSERT_MES(cumulative_size + 1 == txs_size + get_object_blobsize(b.miner_tx), false, "unexpected case: cumulative_size=" << cumulative_size << " + 1 is not equal txs_cumulative_size=" << txs_size << " + get_object_blobsize(b.miner_tx)=" << get_object_blobsize(b.miner_tx)); - b.miner_tx.extra.resize(b.miner_tx.extra.size() - 1); - if (cumulative_size != txs_size + get_object_blobsize(b.miner_tx)) { + if (cumulative_size != txs_size + get_object_blobsize(b.minerTx)) { + CHECK_AND_ASSERT_MES(cumulative_size + 1 == txs_size + get_object_blobsize(b.minerTx), false, "unexpected case: cumulative_size=" << cumulative_size << " + 1 is not equal txs_cumulative_size=" << txs_size << " + get_object_blobsize(b.minerTx)=" << get_object_blobsize(b.minerTx)); + b.minerTx.extra.resize(b.minerTx.extra.size() - 1); + if (cumulative_size != txs_size + get_object_blobsize(b.minerTx)) { //fuck, not lucky, -1 makes varint-counter size smaller, in that case we continue to grow with cumulative_size LOG_PRINT_RED("Miner tx creation have no luck with delta_extra size = " << delta << " and " << delta - 1, LOG_LEVEL_2); cumulative_size += delta - 1; continue; } - LOG_PRINT_GREEN("Setting extra for block: " << b.miner_tx.extra.size() << ", try_count=" << try_count, LOG_LEVEL_1); + LOG_PRINT_GREEN("Setting extra for block: " << b.minerTx.extra.size() << ", try_count=" << try_count, LOG_LEVEL_1); } } - CHECK_AND_ASSERT_MES(cumulative_size == txs_size + get_object_blobsize(b.miner_tx), false, "unexpected case: cumulative_size=" << cumulative_size << " is not equal txs_cumulative_size=" << txs_size << " + get_object_blobsize(b.miner_tx)=" << get_object_blobsize(b.miner_tx)); + CHECK_AND_ASSERT_MES(cumulative_size == txs_size + get_object_blobsize(b.minerTx), false, "unexpected case: cumulative_size=" << cumulative_size << " is not equal txs_cumulative_size=" << txs_size << " + get_object_blobsize(b.minerTx)=" << get_object_blobsize(b.minerTx)); #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) LOG_PRINT_L1("Creating block template: miner tx size " << coinbase_blob_size << ", cumulative size " << cumulative_size << " is now good"); @@ -806,11 +870,11 @@ bool blockchain_storage::create_block_template(block& b, const account_public_ad } bool blockchain_storage::complete_timestamps_vector(uint64_t start_top_height, std::vector& timestamps) { - if (timestamps.size() >= BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW) + if (timestamps.size() >= m_currency.timestampCheckWindow()) return true; CRITICAL_REGION_LOCAL(m_blockchain_lock); - size_t need_elements = BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW - timestamps.size(); + size_t need_elements = m_currency.timestampCheckWindow() - timestamps.size(); CHECK_AND_ASSERT_MES(start_top_height < m_blocks.size(), false, "internal error: passed start_height = " << start_top_height << " not less then m_blocks.size()=" << m_blocks.size()); size_t stop_offset = start_top_height > need_elements ? start_top_height - need_elements : 0; do @@ -823,7 +887,7 @@ bool blockchain_storage::complete_timestamps_vector(uint64_t start_top_height, s return true; } -bool blockchain_storage::handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc) { +bool blockchain_storage::handle_alternative_block(const Block& b, const crypto::hash& id, block_verification_context& bvc) { CRITICAL_REGION_LOCAL(m_blockchain_lock); uint64_t block_height = get_block_height(b); @@ -841,11 +905,33 @@ bool blockchain_storage::handle_alternative_block(const block& b, const crypto:: return false; } + if (!checkBlockVersion(b, id)) { + bvc.m_verifivation_failed = true; + return false; + } + + if (!checkParentBlockSize(b, id)) { + bvc.m_verifivation_failed = true; + return false; + } + + size_t cumulativeSize; + if (!getBlockCumulativeSize(b, cumulativeSize)) { + LOG_PRINT_L2("Block with id: " << id << " has at least one unknown transaction. Cumulative size is calculated imprecisely"); + } + + if (!checkCumulativeBlockSize(id, cumulativeSize, block_height)) { + bvc.m_verifivation_failed = true; + return false; + } + //block is not related with head of main chain //first of all - look in alternative chains container - auto it_main_prev = m_blockMap.find(b.prev_id); - auto it_prev = m_alternative_chains.find(b.prev_id); - if (it_prev != m_alternative_chains.end() || it_main_prev != m_blockMap.end()) { + uint64_t mainPrevHeight = 0; + const bool mainPrev = m_blockIndex.getBlockHeight(b.prevId, mainPrevHeight); + const auto it_prev = m_alternative_chains.find(b.prevId); + + if (it_prev != m_alternative_chains.end() || mainPrev) { //we have new block in alternative chain //build alternative subchain, front -> mainchain, back -> alternative head @@ -855,7 +941,7 @@ bool blockchain_storage::handle_alternative_block(const block& b, const crypto:: while (alt_it != m_alternative_chains.end()) { alt_chain.push_front(alt_it); timestamps.push_back(alt_it->second.bl.timestamp); - alt_it = m_alternative_chains.find(alt_it->second.bl.prev_id); + alt_it = m_alternative_chains.find(alt_it->second.bl.prevId); } if (alt_chain.size()) { @@ -863,11 +949,11 @@ bool blockchain_storage::handle_alternative_block(const block& b, const crypto:: CHECK_AND_ASSERT_MES(m_blocks.size() > alt_chain.front()->second.height, false, "main blockchain wrong height"); crypto::hash h = null_hash; get_block_hash(m_blocks[alt_chain.front()->second.height - 1].bl, h); - CHECK_AND_ASSERT_MES(h == alt_chain.front()->second.bl.prev_id, false, "alternative chain have wrong connection to main chain"); + CHECK_AND_ASSERT_MES(h == alt_chain.front()->second.bl.prevId, false, "alternative chain have wrong connection to main chain"); complete_timestamps_vector(alt_chain.front()->second.height - 1, timestamps); } else { - CHECK_AND_ASSERT_MES(it_main_prev != m_blockMap.end(), false, "internal error: broken imperative condition it_main_prev != m_blocks_index.end()"); - complete_timestamps_vector(it_main_prev->second, timestamps); + CHECK_AND_ASSERT_MES(mainPrev, false, "internal error: broken imperative condition it_main_prev != m_blocks_index.end()"); + complete_timestamps_vector(mainPrevHeight, timestamps); } //check timestamp correct @@ -879,9 +965,9 @@ bool blockchain_storage::handle_alternative_block(const block& b, const crypto:: return false; } - Block bei = boost::value_initialized(); + BlockEntry bei = boost::value_initialized(); bei.bl = b; - bei.height = alt_chain.size() ? it_prev->second.height + 1 : it_main_prev->second + 1; + bei.height = static_cast(alt_chain.size() ? it_prev->second.height + 1 : mainPrevHeight + 1); bool is_a_checkpoint; if (!m_checkpoints.check_block(bei.height, id, is_a_checkpoint)) { @@ -895,8 +981,7 @@ bool blockchain_storage::handle_alternative_block(const block& b, const crypto:: difficulty_type current_diff = get_next_difficulty_for_alternative_chain(alt_chain, bei); CHECK_AND_ASSERT_MES(current_diff, false, "!!!!!!! DIFFICULTY OVERHEAD !!!!!!!"); crypto::hash proof_of_work = null_hash; - get_block_longhash(m_cn_context, bei.bl, proof_of_work, bei.height); - if (!check_hash(proof_of_work, current_diff)) { + if (!m_currency.checkProofOfWork(m_cn_context, bei.bl, current_diff, proof_of_work)) { LOG_PRINT_RED_L0("Block with id: " << id << ENDL << " for alternative chain, have not enough proof of work: " << proof_of_work << ENDL << " expected difficulty: " << current_diff); @@ -910,7 +995,7 @@ bool blockchain_storage::handle_alternative_block(const block& b, const crypto:: return false; } - bei.cumulative_difficulty = alt_chain.size() ? it_prev->second.cumulative_difficulty : m_blocks[it_main_prev->second].cumulative_difficulty; + bei.cumulative_difficulty = alt_chain.size() ? it_prev->second.cumulative_difficulty : m_blocks[mainPrevHeight].cumulative_difficulty; bei.cumulative_difficulty += current_diff; #ifdef _DEBUG @@ -955,7 +1040,7 @@ bool blockchain_storage::handle_alternative_block(const block& b, const crypto:: return true; } -bool blockchain_storage::get_blocks(uint64_t start_offset, size_t count, std::list& blocks, std::list& txs) { +bool blockchain_storage::get_blocks(uint64_t start_offset, size_t count, std::list& blocks, std::list& txs) { CRITICAL_REGION_LOCAL(m_blockchain_lock); if (start_offset >= m_blocks.size()) return false; @@ -963,14 +1048,14 @@ bool blockchain_storage::get_blocks(uint64_t start_offset, size_t count, std::li { blocks.push_back(m_blocks[i].bl); std::list missed_ids; - get_transactions(m_blocks[i].bl.tx_hashes, txs, missed_ids); + get_transactions(m_blocks[i].bl.txHashes, txs, missed_ids); CHECK_AND_ASSERT_MES(!missed_ids.size(), false, "have missed transactions in own block in main blockchain"); } return true; } -bool blockchain_storage::get_blocks(uint64_t start_offset, size_t count, std::list& blocks) { +bool blockchain_storage::get_blocks(uint64_t start_offset, size_t count, std::list& blocks) { CRITICAL_REGION_LOCAL(m_blockchain_lock); if (start_offset >= m_blocks.size()) { return false; @@ -986,26 +1071,26 @@ bool blockchain_storage::get_blocks(uint64_t start_offset, size_t count, std::li bool blockchain_storage::handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp) { CRITICAL_REGION_LOCAL(m_blockchain_lock); rsp.current_blockchain_height = get_current_blockchain_height(); - std::list blocks; + std::list blocks; get_blocks(arg.blocks, blocks, rsp.missed_ids); for (const auto& bl : blocks) { std::list missed_tx_id; - std::list txs; - get_transactions(bl.tx_hashes, txs, rsp.missed_ids); + std::list txs; + get_transactions(bl.txHashes, txs, rsp.missed_ids); CHECK_AND_ASSERT_MES(!missed_tx_id.size(), false, "Internal error: have missed missed_tx_id.size()=" << missed_tx_id.size() << ENDL << "for block id = " << get_block_hash(bl)); rsp.blocks.push_back(block_complete_entry()); block_complete_entry& e = rsp.blocks.back(); //pack block e.block = t_serializable_object_to_blob(bl); //pack transactions - for (transaction& tx : txs) { + for (Transaction& tx : txs) { e.txs.push_back(t_serializable_object_to_blob(tx)); } } //get another transactions, if need - std::list txs; + std::list txs; get_transactions(arg.txs, txs, rsp.missed_ids); //pack aside transactions for (const auto& tx : txs) { @@ -1015,7 +1100,7 @@ bool blockchain_storage::handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& return true; } -bool blockchain_storage::get_alternative_blocks(std::list& blocks) { +bool blockchain_storage::get_alternative_blocks(std::list& blocks) { CRITICAL_REGION_LOCAL(m_blockchain_lock); for (auto& alt_bl : m_alternative_chains) { blocks.push_back(alt_bl.second.bl); @@ -1031,18 +1116,18 @@ size_t blockchain_storage::get_alternative_blocks_count() { bool blockchain_storage::add_out_to_get_random_outs(std::vector>& amount_outs, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs, uint64_t amount, size_t i) { CRITICAL_REGION_LOCAL(m_blockchain_lock); - const transaction& tx = transactionByIndex(amount_outs[i].first).tx; + const Transaction& tx = transactionByIndex(amount_outs[i].first).tx; CHECK_AND_ASSERT_MES(tx.vout.size() > amount_outs[i].second, false, "internal error: in global outs index, transaction out index=" << amount_outs[i].second << " more than transaction outputs = " << tx.vout.size() << ", for tx id = " << get_transaction_hash(tx)); - CHECK_AND_ASSERT_MES(tx.vout[amount_outs[i].second].target.type() == typeid(txout_to_key), false, "unknown tx out type"); + CHECK_AND_ASSERT_MES(tx.vout[amount_outs[i].second].target.type() == typeid(TransactionOutputToKey), false, "unknown tx out type"); //check if transaction is unlocked - if (!is_tx_spendtime_unlocked(tx.unlock_time)) + if (!is_tx_spendtime_unlocked(tx.unlockTime)) return false; COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry& oen = *result_outs.outs.insert(result_outs.outs.end(), COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry()); oen.global_amount_index = i; - oen.out_key = boost::get(tx.vout[amount_outs[i].second].target).key; + oen.out_key = boost::get(tx.vout[amount_outs[i].second].target).key; return true; } @@ -1055,7 +1140,7 @@ size_t blockchain_storage::find_end_of_allowed_index(const std::vectorsecond; - return true; + //this should NEVER happen, but, dose of paranoia in such cases is not too bad + LOG_ERROR("Internal error handling connection, can't find split point"); + return false; } uint64_t blockchain_storage::block_difficulty(size_t i) @@ -1173,7 +1239,7 @@ void blockchain_storage::print_blockchain(uint64_t start_index, uint64_t end_ind { ss << "height " << i << ", timestamp " << m_blocks[i].bl.timestamp << ", cumul_dif " << m_blocks[i].cumulative_difficulty << ", cumul_size " << m_blocks[i].block_cumulative_size << "\nid\t\t" << get_block_hash(m_blocks[i].bl) - << "\ndifficulty\t\t" << block_difficulty(i) << ", nonce " << m_blocks[i].bl.nonce << ", tx_count " << m_blocks[i].bl.tx_hashes.size() << ENDL; + << "\ndifficulty\t\t" << block_difficulty(i) << ", nonce " << m_blocks[i].bl.nonce << ", tx_count " << m_blocks[i].bl.txHashes.size() << ENDL; } LOG_PRINT_L1("Current blockchain:" << ENDL << ss.str()); LOG_PRINT_L0("Blockchain printed with log level 1"); @@ -1182,11 +1248,17 @@ void blockchain_storage::print_blockchain(uint64_t start_index, uint64_t end_ind void blockchain_storage::print_blockchain_index() { std::stringstream ss; CRITICAL_REGION_LOCAL(m_blockchain_lock); - for (auto& i : m_blockMap) { - ss << "id\t\t" << i.first << " height" << i.second << ENDL << ""; + + std::list blockIds; + m_blockIndex.getBlockIds(0, std::numeric_limits::max(), blockIds); + + LOG_PRINT_L0("Current blockchain index:" << ENDL); + + size_t height = 0; + for (auto i = blockIds.begin(); i != blockIds.end(); ++i, ++height) { + LOG_PRINT_L0("id\t\t" << *i << " height" << height); } - LOG_PRINT_L0("Current blockchain index:" << ENDL << ss.str()); } void blockchain_storage::print_blockchain_outs(const std::string& file) { @@ -1216,19 +1288,11 @@ bool blockchain_storage::find_blockchain_supplement(const std::list& qblock_ids, std::list > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) { +bool blockchain_storage::find_blockchain_supplement(const std::list& qblock_ids, std::list > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) { CRITICAL_REGION_LOCAL(m_blockchain_lock); if (!find_blockchain_supplement(qblock_ids, start_height)) { return false; @@ -1240,7 +1304,7 @@ bool blockchain_storage::find_blockchain_supplement(const std::list mis; - get_transactions(m_blocks[i].bl.tx_hashes, blocks.back().second, mis); + get_transactions(m_blocks[i].bl.txHashes, blocks.back().second, mis); CHECK_AND_ASSERT_MES(!mis.size(), false, "internal error, transaction from block not found"); } @@ -1250,7 +1314,7 @@ bool blockchain_storage::find_blockchain_supplement(const std::listsecond); + const TransactionEntry& tx = transactionByIndex(it->second); CHECK_AND_ASSERT_MES(tx.m_global_output_indexes.size(), false, "internal error: global indexes for transaction " << tx_id << " is empty"); indexs.resize(tx.m_global_output_indexes.size()); for (size_t i = 0; i < tx.m_global_output_indexes.size(); ++i) { @@ -1282,8 +1346,12 @@ bool blockchain_storage::get_tx_outputs_gindexs(const crypto::hash& tx_id, std:: return true; } -bool blockchain_storage::check_tx_inputs(const transaction& tx, uint64_t& max_used_block_height, crypto::hash& max_used_block_id) { +bool blockchain_storage::check_tx_inputs(const Transaction& tx, uint64_t& max_used_block_height, crypto::hash& max_used_block_id, BlockInfo* tail) { CRITICAL_REGION_LOCAL(m_blockchain_lock); + + if (tail) + tail->id = get_tail_id(tail->height); + bool res = check_tx_inputs(tx, &max_used_block_height); if (!res) return false; CHECK_AND_ASSERT_MES(max_used_block_height < m_blocks.size(), false, "internal error: max used block index=" << max_used_block_height << " is not less then blockchain size = " << m_blocks.size()); @@ -1291,63 +1359,73 @@ bool blockchain_storage::check_tx_inputs(const transaction& tx, uint64_t& max_us return true; } -bool blockchain_storage::have_tx_keyimges_as_spent(const transaction &tx) { - for(const txin_v& in : tx.vin) { - CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, in_to_key, true); - if (have_tx_keyimg_as_spent(in_to_key.k_image)) { - return true; +bool blockchain_storage::have_tx_keyimges_as_spent(const Transaction &tx) { + for (const auto& in : tx.vin) { + if (in.type() == typeid(TransactionInputToKey)) { + if (have_tx_keyimg_as_spent(boost::get(in).keyImage)) { + return true; + } } } return false; } -bool blockchain_storage::check_tx_inputs(const transaction& tx, uint64_t* pmax_used_block_height) { +bool blockchain_storage::check_tx_inputs(const Transaction& tx, uint64_t* pmax_used_block_height) { crypto::hash tx_prefix_hash = get_transaction_prefix_hash(tx); return check_tx_inputs(tx, tx_prefix_hash, pmax_used_block_height); } -bool blockchain_storage::check_tx_inputs(const transaction& tx, const crypto::hash& tx_prefix_hash, uint64_t* pmax_used_block_height) { - size_t sig_index = 0; +bool blockchain_storage::check_tx_inputs(const Transaction& tx, const crypto::hash& tx_prefix_hash, uint64_t* pmax_used_block_height) { + size_t inputIndex = 0; if (pmax_used_block_height) { *pmax_used_block_height = 0; } + crypto::hash transactionHash = get_transaction_hash(tx); for (const auto& txin : tx.vin) { - CHECK_AND_ASSERT_MES(txin.type() == typeid(txin_to_key), false, "wrong type id in tx input at blockchain_storage::check_tx_inputs"); - const txin_to_key& in_to_key = boost::get(txin); + assert(inputIndex < tx.signatures.size()); + if (txin.type() == typeid(TransactionInputToKey)) { + const TransactionInputToKey& in_to_key = boost::get(txin); + CHECK_AND_ASSERT_MES(!in_to_key.keyOffsets.empty(), false, "empty in_to_key.keyOffsets in transaction with id " << get_transaction_hash(tx)); - CHECK_AND_ASSERT_MES(in_to_key.key_offsets.size(), false, "empty in_to_key.key_offsets in transaction with id " << get_transaction_hash(tx)); + if (have_tx_keyimg_as_spent(in_to_key.keyImage)) { + LOG_PRINT_L1("Key image already spent in blockchain: " << epee::string_tools::pod_to_hex(in_to_key.keyImage)); + return false; + } - if (have_tx_keyimg_as_spent(in_to_key.k_image)) - { - LOG_PRINT_L1("Key image already spent in blockchain: " << epee::string_tools::pod_to_hex(in_to_key.k_image)); + if (!check_tx_input(in_to_key, tx_prefix_hash, tx.signatures[inputIndex], pmax_used_block_height)) { + LOG_PRINT_L0("Failed to check ring signature for tx " << transactionHash); + return false; + } + + ++inputIndex; + } else if (txin.type() == typeid(TransactionInputMultisignature)) { + if (!validateInput(::boost::get(txin), transactionHash, tx_prefix_hash, tx.signatures[inputIndex])) { + return false; + } + + ++inputIndex; + } else { + LOG_PRINT_L0("Transaction << " << transactionHash << " contains input of unsupported type."); return false; } - - CHECK_AND_ASSERT_MES(sig_index < tx.signatures.size(), false, "wrong transaction: not signature entry for input with index= " << sig_index); - if (!check_tx_input(in_to_key, tx_prefix_hash, tx.signatures[sig_index], pmax_used_block_height)) { - LOG_PRINT_L0("Failed to check ring signature for tx " << get_transaction_hash(tx)); - return false; - } - - sig_index++; } return true; } bool blockchain_storage::is_tx_spendtime_unlocked(uint64_t unlock_time) { - if (unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER) { + if (unlock_time < m_currency.maxBlockHeight()) { //interpret as block index - if (get_current_blockchain_height() - 1 + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS >= unlock_time) + if (get_current_blockchain_height() - 1 + m_currency.lockedTxAllowedDeltaBlocks() >= unlock_time) return true; else return false; } else { //interpret as time uint64_t current_time = static_cast(time(NULL)); - if (current_time + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS >= unlock_time) + if (current_time + m_currency.lockedTxAllowedDeltaSeconds() >= unlock_time) return true; else return false; @@ -1356,7 +1434,7 @@ bool blockchain_storage::is_tx_spendtime_unlocked(uint64_t unlock_time) { return false; } -bool blockchain_storage::check_tx_input(const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector& sig, uint64_t* pmax_related_block_height) { +bool blockchain_storage::check_tx_input(const TransactionInputToKey& txin, const crypto::hash& tx_prefix_hash, const std::vector& sig, uint64_t* pmax_related_block_height) { CRITICAL_REGION_LOCAL(m_blockchain_lock); struct outputs_visitor @@ -1365,20 +1443,20 @@ bool blockchain_storage::check_tx_input(const txin_to_key& txin, const crypto::h blockchain_storage& m_bch; outputs_visitor(std::vector& results_collector, blockchain_storage& bch) :m_results_collector(results_collector), m_bch(bch) {} - bool handle_output(const transaction& tx, const tx_out& out) { + bool handle_output(const Transaction& tx, const TransactionOutput& out) { //check tx unlock time - if (!m_bch.is_tx_spendtime_unlocked(tx.unlock_time)) { - LOG_PRINT_L0("One of outputs for one of inputs have wrong tx.unlock_time = " << tx.unlock_time); + if (!m_bch.is_tx_spendtime_unlocked(tx.unlockTime)) { + LOG_PRINT_L0("One of outputs for one of inputs have wrong tx.unlockTime = " << tx.unlockTime); return false; } - if (out.target.type() != typeid(txout_to_key)) + if (out.target.type() != typeid(TransactionOutputToKey)) { LOG_PRINT_L0("Output have wrong type id, which=" << out.target.which()); return false; } - m_results_collector.push_back(&boost::get(out.target).key); + m_results_collector.push_back(&boost::get(out.target).key); return true; } }; @@ -1387,12 +1465,13 @@ bool blockchain_storage::check_tx_input(const txin_to_key& txin, const crypto::h std::vector output_keys; outputs_visitor vi(output_keys, *this); if (!scan_outputkeys_for_indexes(txin, vi, pmax_related_block_height)) { - LOG_PRINT_L0("Failed to get output keys for tx with amount = " << print_money(txin.amount) << " and count indexes " << txin.key_offsets.size()); + LOG_PRINT_L0("Failed to get output keys for tx with amount = " << m_currency.formatAmount(txin.amount) << + " and count indexes " << txin.keyOffsets.size()); return false; } - if (txin.key_offsets.size() != output_keys.size()) { - LOG_PRINT_L0("Output keys for tx with amount = " << txin.amount << " and count indexes " << txin.key_offsets.size() << " returned wrong keys count " << output_keys.size()); + if (txin.keyOffsets.size() != output_keys.size()) { + LOG_PRINT_L0("Output keys for tx with amount = " << txin.amount << " and count indexes " << txin.keyOffsets.size() << " returned wrong keys count " << output_keys.size()); return false; } @@ -1401,7 +1480,7 @@ bool blockchain_storage::check_tx_input(const txin_to_key& txin, const crypto::h return true; } - return crypto::check_ring_signature(tx_prefix_hash, txin.k_image, output_keys, sig.data()); + return crypto::check_ring_signature(tx_prefix_hash, txin.keyImage, output_keys, sig.data()); } uint64_t blockchain_storage::get_adjusted_time() { @@ -1409,14 +1488,14 @@ uint64_t blockchain_storage::get_adjusted_time() { return time(NULL); } -bool blockchain_storage::check_block_timestamp_main(const block& b) { - if (b.timestamp > get_adjusted_time() + CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT) { +bool blockchain_storage::check_block_timestamp_main(const Block& b) { + if (b.timestamp > get_adjusted_time() + m_currency.blockFutureTimeLimit()) { LOG_PRINT_L0("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", bigger than adjusted time + 2 hours"); return false; } std::vector timestamps; - size_t offset = m_blocks.size() <= BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW ? 0 : m_blocks.size() - BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW; + size_t offset = m_blocks.size() <= m_currency.timestampCheckWindow() ? 0 : m_blocks.size() - m_currency.timestampCheckWindow(); for (; offset != m_blocks.size(); ++offset) { timestamps.push_back(m_blocks[offset].bl.timestamp); } @@ -1424,37 +1503,100 @@ bool blockchain_storage::check_block_timestamp_main(const block& b) { return check_block_timestamp(std::move(timestamps), b); } -bool blockchain_storage::check_block_timestamp(std::vector timestamps, const block& b) { - if (timestamps.size() < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW) { +bool blockchain_storage::check_block_timestamp(std::vector timestamps, const Block& b) { + if (timestamps.size() < m_currency.timestampCheckWindow()) { return true; } uint64_t median_ts = epee::misc_utils::median(timestamps); if (b.timestamp < median_ts) { - LOG_PRINT_L0("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", less than median of last " << BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW << " blocks, " << median_ts); + LOG_PRINT_L0("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << + ", less than median of last " << m_currency.timestampCheckWindow() << " blocks, " << median_ts); return false; } return true; } +bool blockchain_storage::checkBlockVersion(const Block& b, const crypto::hash& blockHash) { + uint64_t height = get_block_height(b); + const uint8_t expectedBlockVersion = get_block_major_version_for_height(height); + if (b.majorVersion != expectedBlockVersion) { + LOG_PRINT_L0("Block " << blockHash << " has wrong major version: " << static_cast(b.majorVersion) << + ", at height " << height << " expected version is " << static_cast(expectedBlockVersion)); + return false; + } + + return true; +} + +bool blockchain_storage::checkParentBlockSize(const Block& b, const crypto::hash& blockHash) { + if (BLOCK_MAJOR_VERSION_2 == b.majorVersion) { + auto serializer = makeParentBlockSerializer(b, false, false); + size_t parentBlockSize; + if (!get_object_blobsize(serializer, parentBlockSize)) { + LOG_ERROR("Block " << blockHash << ": failed to determine parent block size"); + return false; + } + + if (parentBlockSize > 2 * 1024) { + LOG_PRINT_L0("Block " << blockHash << " contains too big parent block: " << parentBlockSize << + " bytes, expected no more than " << 2 * 1024 << " bytes"); + return false; + } + } + + return true; +} + +bool blockchain_storage::checkCumulativeBlockSize(const crypto::hash& blockId, size_t cumulativeBlockSize, uint64_t height) { + size_t maxBlockCumulativeSize = m_currency.maxBlockCumulativeSize(height); + if (cumulativeBlockSize > maxBlockCumulativeSize) { + LOG_PRINT_L0("Block " << blockId << " is too big: " << cumulativeBlockSize << " bytes, " << + "exptected no more than " << maxBlockCumulativeSize << " bytes"); + return false; + } + + return true; +} + +// Returns true, if cumulativeSize is calculated precisely, else returns false. +bool blockchain_storage::getBlockCumulativeSize(const Block& block, size_t& cumulativeSize) { + std::vector blockTxs; + std::vector missedTxs; + get_transactions(block.txHashes, blockTxs, missedTxs, true); + + cumulativeSize = get_object_blobsize(block.minerTx); + for (const Transaction& tx : blockTxs) { + cumulativeSize += get_object_blobsize(tx); + } + + return missedTxs.empty(); +} + bool blockchain_storage::update_next_comulative_size_limit() { std::vector sz; - get_last_n_blocks_sizes(sz, CRYPTONOTE_REWARD_BLOCKS_WINDOW); + get_last_n_blocks_sizes(sz, m_currency.rewardBlocksWindow()); uint64_t median = epee::misc_utils::median(sz); - if (median <= CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE) - median = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE; + if (median <= m_currency.blockGrantedFullRewardZone()) + median = m_currency.blockGrantedFullRewardZone(); m_current_block_cumul_sz_limit = median * 2; return true; } -bool blockchain_storage::add_new_block(const block& bl_, block_verification_context& bvc) { +bool blockchain_storage::add_new_block(const Block& bl_, block_verification_context& bvc) { //copy block here to let modify block.target - block bl = bl_; - crypto::hash id = get_block_hash(bl); + Block bl = bl_; + crypto::hash id; + if (!get_block_hash(bl, id)) { + LOG_ERROR("Failed to get block hash, possible block has invalid format"); + bvc.m_verifivation_failed = true; + return false; + } + CRITICAL_REGION_LOCAL(m_tx_pool);//to avoid deadlock lets lock tx_pool for whole add/reorganize process CRITICAL_REGION_LOCAL1(m_blockchain_lock); if (have_block(id)) { @@ -1464,7 +1606,7 @@ bool blockchain_storage::add_new_block(const block& bl_, block_verification_cont } //check that block refers to chain tail - if (!(bl.prev_id == get_tail_id())) { + if (!(bl.prevId == get_tail_id())) { //chain switching or wrong block bvc.m_added_to_main_chain = false; return handle_alternative_block(bl, id, bvc); @@ -1474,24 +1616,34 @@ bool blockchain_storage::add_new_block(const block& bl_, block_verification_cont return pushBlock(bl, bvc); } -const blockchain_storage::Transaction& blockchain_storage::transactionByIndex(TransactionIndex index) { +const blockchain_storage::TransactionEntry& blockchain_storage::transactionByIndex(TransactionIndex index) { return m_blocks[index.block].transactions[index.transaction]; } -bool blockchain_storage::pushBlock(const block& blockData, block_verification_context& bvc) { +bool blockchain_storage::pushBlock(const Block& blockData, block_verification_context& bvc) { CRITICAL_REGION_LOCAL(m_blockchain_lock); TIME_MEASURE_START(block_processing_time); crypto::hash blockHash = get_block_hash(blockData); - if (m_blockMap.count(blockHash) != 0) { + if (m_blockIndex.hasBlock(blockHash)) { LOG_ERROR("Block " << blockHash << " already exists in blockchain."); bvc.m_verifivation_failed = true; return false; } - if (blockData.prev_id != get_tail_id()) { - LOG_PRINT_L0("Block " << blockHash << " has wrong prev_id: " << blockData.prev_id << ", expected: " << get_tail_id()); + if (!checkBlockVersion(blockData, blockHash)) { + bvc.m_verifivation_failed = true; + return false; + } + + if (!checkParentBlockSize(blockData, blockHash)) { + bvc.m_verifivation_failed = true; + return false; + } + + if (blockData.prevId != get_tail_id()) { + LOG_PRINT_L0("Block " << blockHash << " has wrong prevId: " << blockData.prevId << ", expected: " << get_tail_id()); bvc.m_verifivation_failed = true; return false; } @@ -1516,8 +1668,7 @@ bool blockchain_storage::pushBlock(const block& blockData, block_verification_co return false; } } else { - proof_of_work = get_block_longhash(m_cn_context, blockData, m_blocks.size()); - if (!check_hash(proof_of_work, currentDifficulty)) { + if (!m_currency.checkProofOfWork(m_cn_context, blockData, currentDifficulty, proof_of_work)) { LOG_PRINT_L0("Block " << blockHash << ", has too weak proof of work: " << proof_of_work << ", expected difficulty: " << currentDifficulty); bvc.m_verifivation_failed = true; return false; @@ -1532,19 +1683,19 @@ bool blockchain_storage::pushBlock(const block& blockData, block_verification_co return false; } - crypto::hash minerTransactionHash = get_transaction_hash(blockData.miner_tx); + crypto::hash minerTransactionHash = get_transaction_hash(blockData.minerTx); - Block block; + BlockEntry block; block.bl = blockData; block.transactions.resize(1); - block.transactions[0].tx = blockData.miner_tx; + block.transactions[0].tx = blockData.minerTx; TransactionIndex transactionIndex = { static_cast(m_blocks.size()), static_cast(0) }; pushTransaction(block, minerTransactionHash, transactionIndex); - size_t coinbase_blob_size = get_object_blobsize(blockData.miner_tx); + size_t coinbase_blob_size = get_object_blobsize(blockData.minerTx); size_t cumulative_block_size = coinbase_blob_size; uint64_t fee_summary = 0; - for (const crypto::hash& tx_id : blockData.tx_hashes) { + for (const crypto::hash& tx_id : blockData.txHashes) { block.transactions.resize(block.transactions.size() + 1); size_t blob_size = 0; uint64_t fee = 0; @@ -1577,9 +1728,15 @@ bool blockchain_storage::pushBlock(const block& blockData, block_verification_co fee_summary += fee; } - uint64_t base_reward = 0; - uint64_t already_generated_coins = m_blocks.size() ? m_blocks.back().already_generated_coins : 0; - if (!validate_miner_transaction(blockData, cumulative_block_size, fee_summary, base_reward, already_generated_coins)) { + if (!checkCumulativeBlockSize(blockHash, cumulative_block_size, m_blocks.size())) { + bvc.m_verifivation_failed = true; + return false; + } + + int64_t emissionChange = 0; + uint64_t reward = 0; + uint64_t already_generated_coins = m_blocks.empty() ? 0 : m_blocks.back().already_generated_coins; + if (!validate_miner_transaction(blockData, m_blocks.size(), cumulative_block_size, already_generated_coins, fee_summary, reward, emissionChange)) { LOG_PRINT_L0("Block " << blockHash << " has invalid miner transaction"); bvc.m_verifivation_failed = true; popTransactions(block, minerTransactionHash); @@ -1589,7 +1746,7 @@ bool blockchain_storage::pushBlock(const block& blockData, block_verification_co block.height = static_cast(m_blocks.size()); block.block_cumulative_size = cumulative_block_size; block.cumulative_difficulty = currentDifficulty; - block.already_generated_coins = already_generated_coins + base_reward; + block.already_generated_coins = already_generated_coins + emissionChange; if (m_blocks.size() > 0) { block.cumulative_difficulty += m_blocks.back().cumulative_difficulty; } @@ -1600,23 +1757,25 @@ bool blockchain_storage::pushBlock(const block& blockData, block_verification_co LOG_PRINT_L1("+++++ BLOCK SUCCESSFULLY ADDED" << ENDL << "id:\t" << blockHash << ENDL << "PoW:\t" << proof_of_work << ENDL << "HEIGHT " << block.height << ", difficulty:\t" << currentDifficulty - << ENDL << "block reward: " << print_money(fee_summary + base_reward) << "(" << print_money(base_reward) << " + " << print_money(fee_summary) - << "), coinbase_blob_size: " << coinbase_blob_size << ", cumulative size: " << cumulative_block_size + << ENDL << "block reward: " << m_currency.formatAmount(reward) << ", fee = " << m_currency.formatAmount(fee_summary) + << ", coinbase_blob_size: " << coinbase_blob_size << ", cumulative size: " << cumulative_block_size << ", " << block_processing_time << "(" << target_calculating_time << "/" << longhash_calculating_time << ")ms"); bvc.m_added_to_main_chain = true; + + m_upgradeDetector.blockPushed(); + return true; } -bool blockchain_storage::pushBlock(Block& block) { +bool blockchain_storage::pushBlock(BlockEntry& block) { crypto::hash blockHash = get_block_hash(block.bl); - auto result = m_blockMap.insert(std::make_pair(blockHash, static_cast(m_blocks.size()))); - if (!result.second) { - LOG_ERROR("Duplicate block was pushed to blockchain."); - return false; - } m_blocks.push_back(block); + m_blockIndex.push(blockHash); + + assert(m_blockIndex.size() == m_blocks.size()); + return true; } @@ -1626,29 +1785,37 @@ void blockchain_storage::popBlock(const crypto::hash& blockHash) { return; } - popTransactions(m_blocks.back(), get_transaction_hash(m_blocks.back().bl.miner_tx)); + popTransactions(m_blocks.back(), get_transaction_hash(m_blocks.back().bl.minerTx)); m_blocks.pop_back(); - size_t count = m_blockMap.erase(blockHash); - if (count != 1) { - LOG_ERROR("Blockchain consistency broken - cannot find block by hash."); - } + m_blockIndex.pop(); + + assert(m_blockIndex.size() == m_blocks.size()); + + m_upgradeDetector.blockPopped(); } -bool blockchain_storage::pushTransaction(Block& block, const crypto::hash& transactionHash, TransactionIndex transactionIndex) { +bool blockchain_storage::pushTransaction(BlockEntry& block, const crypto::hash& transactionHash, TransactionIndex transactionIndex) { auto result = m_transactionMap.insert(std::make_pair(transactionHash, transactionIndex)); if (!result.second) { LOG_ERROR("Duplicate transaction was pushed to blockchain."); return false; } - Transaction& transaction = block.transactions[transactionIndex.transaction]; + TransactionEntry& transaction = block.transactions[transactionIndex.transaction]; + + if (!checkMultisignatureInputsDiff(transaction.tx)) { + LOG_ERROR("Double spending transaction was pushed to blockchain."); + m_transactionMap.erase(transactionHash); + return false; + } + for (size_t i = 0; i < transaction.tx.vin.size(); ++i) { - if (transaction.tx.vin[i].type() == typeid(txin_to_key)) { - auto result = m_spent_keys.insert(::boost::get(transaction.tx.vin[i]).k_image); + if (transaction.tx.vin[i].type() == typeid(TransactionInputToKey)) { + auto result = m_spent_keys.insert(::boost::get(transaction.tx.vin[i]).keyImage); if (!result.second) { LOG_ERROR("Double spending transaction was pushed to blockchain."); for (size_t j = 0; j < i; ++j) { - m_spent_keys.erase(::boost::get(transaction.tx.vin[i - 1 - j]).k_image); + m_spent_keys.erase(::boost::get(transaction.tx.vin[i - 1 - j]).keyImage); } m_transactionMap.erase(transactionHash); @@ -1657,52 +1824,108 @@ bool blockchain_storage::pushTransaction(Block& block, const crypto::hash& trans } } + for (const auto& inv : transaction.tx.vin) { + if (inv.type() == typeid(TransactionInputMultisignature)) { + const TransactionInputMultisignature& in = ::boost::get(inv); + auto& amountOutputs = m_multisignatureOutputs[in.amount]; + amountOutputs[in.outputIndex].isUsed = true; + } + } + transaction.m_global_output_indexes.resize(transaction.tx.vout.size()); for (uint16_t output = 0; output < transaction.tx.vout.size(); ++output) { - auto& amountOutputs = m_outputs[transaction.tx.vout[output].amount]; - transaction.m_global_output_indexes[output] = amountOutputs.size(); - amountOutputs.push_back(std::make_pair<>(transactionIndex, output)); + if (transaction.tx.vout[output].target.type() == typeid(TransactionOutputToKey)) { + auto& amountOutputs = m_outputs[transaction.tx.vout[output].amount]; + transaction.m_global_output_indexes[output] = amountOutputs.size(); + amountOutputs.push_back(std::make_pair<>(transactionIndex, output)); + } else if (transaction.tx.vout[output].target.type() == typeid(TransactionOutputMultisignature)) { + auto& amountOutputs = m_multisignatureOutputs[transaction.tx.vout[output].amount]; + MultisignatureOutputUsage outputUsage = {transactionIndex, output, false}; + amountOutputs.push_back(outputUsage); + } } return true; } -void blockchain_storage::popTransaction(const transaction& transaction, const crypto::hash& transactionHash) { +void blockchain_storage::popTransaction(const Transaction& transaction, const crypto::hash& transactionHash) { TransactionIndex transactionIndex = m_transactionMap.at(transactionHash); - for (size_t output = 0; output < transaction.vout.size(); ++output) { - auto amountOutputs = m_outputs.find(transaction.vout[transaction.vout.size() - 1 - output].amount); - if (amountOutputs == m_outputs.end()) { - LOG_ERROR("Blockchain consistency broken - cannot find specific amount in outputs map."); - continue; - } + for (size_t outputIndex = 0; outputIndex < transaction.vout.size(); ++outputIndex) { + const TransactionOutput& output = transaction.vout[transaction.vout.size() - 1 - outputIndex]; + if (output.target.type() == typeid(TransactionOutputToKey)) { + auto amountOutputs = m_outputs.find(output.amount); + if (amountOutputs == m_outputs.end()) { + LOG_ERROR("Blockchain consistency broken - cannot find specific amount in outputs map."); + continue; + } - if (amountOutputs->second.empty()) { - LOG_ERROR("Blockchain consistency broken - output array for specific amount is empty."); - continue; - } + if (amountOutputs->second.empty()) { + LOG_ERROR("Blockchain consistency broken - output array for specific amount is empty."); + continue; + } - if (amountOutputs->second.back().first.block != transactionIndex.block || amountOutputs->second.back().first.transaction != transactionIndex.transaction) { - LOG_ERROR("Blockchain consistency broken - invalid transaction index."); - continue; - } + if (amountOutputs->second.back().first.block != transactionIndex.block || amountOutputs->second.back().first.transaction != transactionIndex.transaction) { + LOG_ERROR("Blockchain consistency broken - invalid transaction index."); + continue; + } - if (amountOutputs->second.back().second != transaction.vout.size() - 1 - output) { - LOG_ERROR("Blockchain consistency broken - invalid output index."); - continue; - } + if (amountOutputs->second.back().second != transaction.vout.size() - 1 - outputIndex) { + LOG_ERROR("Blockchain consistency broken - invalid output index."); + continue; + } - amountOutputs->second.pop_back(); - if (amountOutputs->second.empty()) { - m_outputs.erase(amountOutputs); + amountOutputs->second.pop_back(); + if (amountOutputs->second.empty()) { + m_outputs.erase(amountOutputs); + } + } else if (output.target.type() == typeid(TransactionOutputMultisignature)) { + auto amountOutputs = m_multisignatureOutputs.find(output.amount); + if (amountOutputs == m_multisignatureOutputs.end()) { + LOG_ERROR("Blockchain consistency broken - cannot find specific amount in outputs map."); + continue; + } + + if (amountOutputs->second.empty()) { + LOG_ERROR("Blockchain consistency broken - output array for specific amount is empty."); + continue; + } + + if (amountOutputs->second.back().isUsed) { + LOG_ERROR("Blockchain consistency broken - attempting to remove used output."); + continue; + } + + if (amountOutputs->second.back().transactionIndex.block != transactionIndex.block || amountOutputs->second.back().transactionIndex.transaction != transactionIndex.transaction) { + LOG_ERROR("Blockchain consistency broken - invalid transaction index."); + continue; + } + + if (amountOutputs->second.back().outputIndex != transaction.vout.size() - 1 - outputIndex) { + LOG_ERROR("Blockchain consistency broken - invalid output index."); + continue; + } + + amountOutputs->second.pop_back(); + if (amountOutputs->second.empty()) { + m_multisignatureOutputs.erase(amountOutputs); + } } } for (auto& input : transaction.vin) { - if (input.type() == typeid(txin_to_key)) { - size_t count = m_spent_keys.erase(::boost::get(input).k_image); + if (input.type() == typeid(TransactionInputToKey)) { + size_t count = m_spent_keys.erase(::boost::get(input).keyImage); if (count != 1) { LOG_ERROR("Blockchain consistency broken - cannot find spent key."); } + } else if (input.type() == typeid(TransactionInputMultisignature)) { + const TransactionInputMultisignature& in = ::boost::get(input); + auto& amountOutputs = m_multisignatureOutputs[in.amount]; + if (!amountOutputs[in.outputIndex].isUsed) { + LOG_ERROR("Blockchain consistency broken - multisignature output not marked as used."); + } + + amountOutputs[in.outputIndex].isUsed = false; } } @@ -1712,14 +1935,88 @@ void blockchain_storage::popTransaction(const transaction& transaction, const cr } } -void blockchain_storage::popTransactions(const Block& block, const crypto::hash& minerTransactionHash) { +void blockchain_storage::popTransactions(const BlockEntry& block, const crypto::hash& minerTransactionHash) { for (size_t i = 0; i < block.transactions.size() - 1; ++i) { - popTransaction(block.transactions[block.transactions.size() - 1 - i].tx, block.bl.tx_hashes[block.transactions.size() - 2 - i]); + popTransaction(block.transactions[block.transactions.size() - 1 - i].tx, block.bl.txHashes[block.transactions.size() - 2 - i]); tx_verification_context tvc = ::AUTO_VAL_INIT(tvc); if (!m_tx_pool.add_tx(block.transactions[block.transactions.size() - 1 - i].tx, tvc, true)) { LOG_ERROR("Cannot move transaction from blockchain to transaction pool."); } } - popTransaction(block.bl.miner_tx, minerTransactionHash); + popTransaction(block.bl.minerTx, minerTransactionHash); +} + +bool blockchain_storage::validateInput(const TransactionInputMultisignature& input, const crypto::hash& transactionHash, const crypto::hash& transactionPrefixHash, const std::vector& transactionSignatures) { + assert(input.signatures == transactionSignatures.size()); + MultisignatureOutputsContainer::const_iterator amountOutputs = m_multisignatureOutputs.find(input.amount); + if (amountOutputs == m_multisignatureOutputs.end()) { + LOG_PRINT_L1("Transaction << " << transactionHash << " contains multisignature input with invalid amount."); + return false; + } + + if (input.outputIndex >= amountOutputs->second.size()) { + LOG_PRINT_L1("Transaction << " << transactionHash << " contains multisignature input with invalid outputIndex."); + return false; + } + + const MultisignatureOutputUsage& outputIndex = amountOutputs->second[input.outputIndex]; + if (outputIndex.isUsed) { + LOG_PRINT_L1("Transaction << " << transactionHash << " contains double spending multisignature input."); + return false; + } + + const Transaction& outputTransaction = m_blocks[outputIndex.transactionIndex.block].transactions[outputIndex.transactionIndex.transaction].tx; + if (!is_tx_spendtime_unlocked(outputTransaction.unlockTime)) { + LOG_PRINT_L1("Transaction << " << transactionHash << " contains multisignature input which points to a locked transaction."); + return false; + } + + assert(outputTransaction.vout[outputIndex.outputIndex].amount == input.amount); + assert(outputTransaction.vout[outputIndex.outputIndex].target.type() == typeid(TransactionOutputMultisignature)); + const TransactionOutputMultisignature& output = ::boost::get(outputTransaction.vout[outputIndex.outputIndex].target); + if (input.signatures != output.requiredSignatures) { + LOG_PRINT_L1("Transaction << " << transactionHash << " contains multisignature input with invalid signature count."); + return false; + } + + std::size_t inputSignatureIndex = 0; + std::size_t outputKeyIndex = 0; + while (inputSignatureIndex < input.signatures) { + if (outputKeyIndex == output.keys.size()) { + LOG_PRINT_L1("Transaction << " << transactionHash << " contains multisignature input with invalid signatures."); + return false; + } + + if (crypto::check_signature(transactionPrefixHash, output.keys[outputKeyIndex], transactionSignatures[inputSignatureIndex])) { + ++inputSignatureIndex; + } + + ++outputKeyIndex; + } + + return true; +} + +bool blockchain_storage::getLowerBound(uint64_t timestamp, uint64_t startOffset, uint64_t& height) { + CRITICAL_REGION_LOCAL(m_blockchain_lock); + + if (startOffset >= m_blocks.size()) { + return false; + } + + auto bound = std::lower_bound(m_blocks.begin() + startOffset, m_blocks.end(), timestamp - m_currency.blockFutureTimeLimit(), + [](const BlockEntry& b, uint64_t timestamp) { return b.bl.timestamp < timestamp; }); + + if (bound == m_blocks.end()) { + return false; + } + + height = std::distance(m_blocks.begin(), bound); + return true; +} + +bool blockchain_storage::getBlockIds(uint64_t startHeight, size_t maxCount, std::list& items) { + CRITICAL_REGION_LOCAL(m_blockchain_lock); + return m_blockIndex.getBlockIds(startHeight, maxCount, items); } diff --git a/src/cryptonote_core/blockchain_storage.h b/src/cryptonote_core/blockchain_storage.h index 6b6b119c..65e6a432 100644 --- a/src/cryptonote_core/blockchain_storage.h +++ b/src/cryptonote_core/blockchain_storage.h @@ -17,54 +17,80 @@ #pragma once +#include + +#include "Currency.h" #include "SwappedVector.h" +#include "UpgradeDetector.h" #include "cryptonote_format_utils.h" #include "tx_pool.h" #include "common/util.h" -#include "rpc/core_rpc_server_commands_defs.h" #include "checkpoints.h" +#include "google/sparse_hash_set" +#include "google/sparse_hash_map" + +#include "ITransactionValidator.h" +#include "BlockIndex.h" + namespace cryptonote { - class blockchain_storage { + struct NOTIFY_RESPONSE_CHAIN_ENTRY_request; + struct NOTIFY_REQUEST_GET_OBJECTS_request; + struct NOTIFY_RESPONSE_GET_OBJECTS_request; + struct COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_request; + struct COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_response; + struct COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_outs_for_amount; + + using CryptoNote::BlockInfo; + class blockchain_storage : public CryptoNote::ITransactionValidator { public: - blockchain_storage(tx_memory_pool& tx_pool):m_tx_pool(tx_pool), m_current_block_cumul_sz_limit(0), m_is_in_checkpoint_zone(false), m_is_blockchain_storing(false) - {}; + blockchain_storage(const Currency& currency, tx_memory_pool& tx_pool); + + // ITransactionValidator + virtual bool checkTransactionInputs(const cryptonote::Transaction& tx, BlockInfo& maxUsedBlock); + virtual bool checkTransactionInputs(const cryptonote::Transaction& tx, BlockInfo& maxUsedBlock, BlockInfo& lastFailed); + virtual bool haveSpentKeyImages(const cryptonote::Transaction& tx); bool init() { return init(tools::get_default_data_dir(), true); } bool init(const std::string& config_folder, bool load_existing); bool deinit(); + bool getLowerBound(uint64_t timestamp, uint64_t startOffset, uint64_t& height); + bool getBlockIds(uint64_t startHeight, size_t maxCount, std::list& items); + void set_checkpoints(checkpoints&& chk_pts) { m_checkpoints = chk_pts; } - bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks, std::list& txs); - bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks); - bool get_alternative_blocks(std::list& blocks); + bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks, std::list& txs); + bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks); + bool get_alternative_blocks(std::list& blocks); size_t get_alternative_blocks_count(); crypto::hash get_block_id_by_height(uint64_t height); - bool get_block_by_hash(const crypto::hash &h, block &blk); + bool get_block_by_hash(const crypto::hash &h, Block &blk); template void serialize(archive_t & ar, const unsigned int version); bool have_tx(const crypto::hash &id); - bool have_tx_keyimges_as_spent(const transaction &tx); + bool have_tx_keyimges_as_spent(const Transaction &tx); uint64_t get_current_blockchain_height(); crypto::hash get_tail_id(); crypto::hash get_tail_id(uint64_t& height); difficulty_type get_difficulty_for_next_block(); - bool add_new_block(const block& bl_, block_verification_context& bvc); - bool reset_and_set_genesis_block(const block& b); - bool create_block_template(block& b, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, const blobdata& ex_nonce); + uint64_t getCoinsInCirculation(); + uint8_t get_block_major_version_for_height(uint64_t height) const; + bool add_new_block(const Block& bl_, block_verification_context& bvc); + bool reset_and_set_genesis_block(const Block& b); + bool create_block_template(Block& b, const AccountPublicAddress& miner_address, difficulty_type& di, uint64_t& height, const blobdata& ex_nonce); bool have_block(const crypto::hash& id); size_t get_total_transactions(); bool get_short_chain_history(std::list& ids); - bool find_blockchain_supplement(const std::list& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp); - bool find_blockchain_supplement(const std::list& qblock_ids, std::list>>& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count); - bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp); - bool get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res); + bool find_blockchain_supplement(const std::list& qblock_ids, uint64_t& starter_offset); // !!!! + bool find_blockchain_supplement(const std::list& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY_request& resp); + bool find_blockchain_supplement(const std::list& qblock_ids, std::list>>& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count); + bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS_request& arg, NOTIFY_RESPONSE_GET_OBJECTS_request& rsp); + bool get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_response& res); bool get_backward_blocks_sizes(size_t from_height, std::vector& sz, size_t count); bool get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector& indexs); - bool store_blockchain(); - bool check_tx_inputs(const transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id); + bool check_tx_inputs(const Transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id, BlockInfo* tail = 0); uint64_t get_current_comulative_blocksize_limit(); bool is_storing_blockchain(){return m_is_blockchain_storing;} uint64_t block_difficulty(size_t i); @@ -74,13 +100,13 @@ namespace cryptonote { CRITICAL_REGION_LOCAL(m_blockchain_lock); for (const auto& bl_id : block_ids) { - auto it = m_blockMap.find(bl_id); - if (it == m_blockMap.end()) { + uint64_t height = 0; + if (!m_blockIndex.getBlockHeight(bl_id, height)) { missed_bs.push_back(bl_id); } else { - CHECK_AND_ASSERT_MES(it->second < m_blocks.size(), false, "Internal error: bl_id=" << epee::string_tools::pod_to_hex(bl_id) - << " have index record with offset="<second<< ", bigger then m_blocks.size()=" << m_blocks.size()); - blocks.push_back(m_blocks[it->second].bl); + CHECK_AND_ASSERT_MES(height < m_blocks.size(), false, "Internal error: bl_id=" << epee::string_tools::pod_to_hex(bl_id) + << " have index record with offset=" << height << ", bigger then m_blocks.size()=" << m_blocks.size()); + blocks.push_back(m_blocks[height].bl); } } @@ -88,7 +114,7 @@ namespace cryptonote { } template - void get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) { + void get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs, bool checkTxPool = false) { CRITICAL_REGION_LOCAL(m_blockchain_lock); for (const auto& tx_id : txs_ids) { @@ -99,6 +125,12 @@ namespace cryptonote { txs.push_back(transactionByIndex(it->second).tx); } } + + if (checkTxPool) { + auto poolTxIds = std::move(missed_txs); + missed_txs.clear(); + m_tx_pool.getTransactions(poolTxIds, txs, missed_txs); + } } //debug functions @@ -107,8 +139,8 @@ namespace cryptonote { void print_blockchain_outs(const std::string& file); private: - struct Transaction { - transaction tx; + struct TransactionEntry { + Transaction tx; std::vector m_global_output_indexes; template void serialize(archive_t & ar, unsigned int version); @@ -119,13 +151,13 @@ namespace cryptonote { END_SERIALIZE() }; - struct Block { - block bl; + struct BlockEntry { + Block bl; uint32_t height; uint64_t block_cumulative_size; difficulty_type cumulative_difficulty; uint64_t already_generated_coins; - std::vector transactions; + std::vector transactions; template void serialize(Archive& archive, unsigned int version); @@ -146,10 +178,20 @@ namespace cryptonote { template void serialize(Archive& archive, unsigned int version); }; - typedef std::unordered_set key_images_container; - typedef std::unordered_map blocks_ext_by_hash; - typedef std::map>> outputs_container; //crypto::hash - tx hash, size_t - index of out in transaction + struct MultisignatureOutputUsage { + TransactionIndex transactionIndex; + uint16_t outputIndex; + bool isUsed; + template void serialize(Archive& archive, unsigned int version); + }; + + typedef google::sparse_hash_set key_images_container; + typedef std::unordered_map blocks_ext_by_hash; + typedef google::sparse_hash_map>> outputs_container; //crypto::hash - tx hash, size_t - index of out in transaction + typedef std::map> MultisignatureOutputsContainer; + + const Currency& m_currency; tx_memory_pool& m_tx_pool; epee::critical_section m_blockchain_lock; // TODO: add here reader/writer lock crypto::cn_context m_cn_context; @@ -164,53 +206,80 @@ namespace cryptonote { std::atomic m_is_in_checkpoint_zone; std::atomic m_is_blockchain_storing; - typedef SwappedVector Blocks; + typedef SwappedVector Blocks; typedef std::unordered_map BlockMap; typedef std::unordered_map TransactionMap; + typedef BasicUpgradeDetector UpgradeDetector; + + friend class BlockCacheSerializer; Blocks m_blocks; - BlockMap m_blockMap; + CryptoNote::BlockIndex m_blockIndex; TransactionMap m_transactionMap; + MultisignatureOutputsContainer m_multisignatureOutputs; + UpgradeDetector m_upgradeDetector; - template bool scan_outputkeys_for_indexes(const txin_to_key& tx_in_to_key, visitor_t& vis, uint64_t* pmax_related_block_height = NULL); + bool storeCache(); + template bool scan_outputkeys_for_indexes(const TransactionInputToKey& tx_in_to_key, visitor_t& vis, uint64_t* pmax_related_block_height = NULL); bool switch_to_alternative_blockchain(std::list& alt_chain, bool discard_disconnected_chain); - bool handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc); - difficulty_type get_next_difficulty_for_alternative_chain(const std::list& alt_chain, Block& bei); - bool prevalidate_miner_transaction(const block& b, uint64_t height); - bool validate_miner_transaction(const block& b, size_t cumulative_block_size, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins); - bool validate_transaction(const block& b, uint64_t height, const transaction& tx); - bool rollback_blockchain_switching(std::list& original_chain, size_t rollback_height); + bool handle_alternative_block(const Block& b, const crypto::hash& id, block_verification_context& bvc); + difficulty_type get_next_difficulty_for_alternative_chain(const std::list& alt_chain, BlockEntry& bei); + bool prevalidate_miner_transaction(const Block& b, uint64_t height); + bool validate_miner_transaction(const Block& b, uint64_t height, size_t cumulativeBlockSize, uint64_t alreadyGeneratedCoins, uint64_t fee, uint64_t& reward, int64_t& emissionChange); + bool validate_transaction(const Block& b, uint64_t height, const Transaction& tx); + bool rollback_blockchain_switching(std::list& original_chain, size_t rollback_height); bool get_last_n_blocks_sizes(std::vector& sz, size_t count); - bool add_out_to_get_random_outs(std::vector>& amount_outs, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs, uint64_t amount, size_t i); + bool add_out_to_get_random_outs(std::vector>& amount_outs, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_outs_for_amount& result_outs, uint64_t amount, size_t i); bool is_tx_spendtime_unlocked(uint64_t unlock_time); size_t find_end_of_allowed_index(const std::vector>& amount_outs); - bool check_block_timestamp_main(const block& b); - bool check_block_timestamp(std::vector timestamps, const block& b); + bool check_block_timestamp_main(const Block& b); + bool check_block_timestamp(std::vector timestamps, const Block& b); uint64_t get_adjusted_time(); bool complete_timestamps_vector(uint64_t start_height, std::vector& timestamps); + bool checkBlockVersion(const Block& b, const crypto::hash& blockHash); + bool checkParentBlockSize(const Block& b, const crypto::hash& blockHash); + bool checkCumulativeBlockSize(const crypto::hash& blockId, size_t cumulativeBlockSize, uint64_t height); + bool getBlockCumulativeSize(const Block& block, size_t& cumulativeSize); bool update_next_comulative_size_limit(); - bool find_blockchain_supplement(const std::list& qblock_ids, uint64_t& starter_offset); - bool check_tx_input(const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector& sig, uint64_t* pmax_related_block_height = NULL); - bool check_tx_inputs(const transaction& tx, const crypto::hash& tx_prefix_hash, uint64_t* pmax_used_block_height = NULL); - bool check_tx_inputs(const transaction& tx, uint64_t* pmax_used_block_height = NULL); + bool check_tx_input(const TransactionInputToKey& txin, const crypto::hash& tx_prefix_hash, const std::vector& sig, uint64_t* pmax_related_block_height = NULL); + bool check_tx_inputs(const Transaction& tx, const crypto::hash& tx_prefix_hash, uint64_t* pmax_used_block_height = NULL); + bool check_tx_inputs(const Transaction& tx, uint64_t* pmax_used_block_height = NULL); bool have_tx_keyimg_as_spent(const crypto::key_image &key_im); - const Transaction& transactionByIndex(TransactionIndex index); - bool pushBlock(const block& blockData, block_verification_context& bvc); - bool pushBlock(Block& block); + const TransactionEntry& transactionByIndex(TransactionIndex index); + bool pushBlock(const Block& blockData, block_verification_context& bvc); + bool pushBlock(BlockEntry& block); void popBlock(const crypto::hash& blockHash); - bool pushTransaction(Block& block, const crypto::hash& transactionHash, TransactionIndex transactionIndex); - void popTransaction(const transaction& transaction, const crypto::hash& transactionHash); - void popTransactions(const Block& block, const crypto::hash& minerTransactionHash); + bool pushTransaction(BlockEntry& block, const crypto::hash& transactionHash, TransactionIndex transactionIndex); + void popTransaction(const Transaction& transaction, const crypto::hash& transactionHash); + void popTransactions(const BlockEntry& block, const crypto::hash& minerTransactionHash); + bool validateInput(const TransactionInputMultisignature& input, const crypto::hash& transactionHash, const crypto::hash& transactionPrefixHash, const std::vector& transactionSignatures); + + friend class LockedBlockchainStorage; }; + class LockedBlockchainStorage: boost::noncopyable { + public: - template bool blockchain_storage::scan_outputkeys_for_indexes(const txin_to_key& tx_in_to_key, visitor_t& vis, uint64_t* pmax_related_block_height) { + LockedBlockchainStorage(blockchain_storage& bc) + : m_bc(bc), m_lock(bc.m_blockchain_lock) {} + + blockchain_storage* operator -> () { + return &m_bc; + } + + private: + + blockchain_storage& m_bc; + epee::critical_region_t m_lock; + }; + + template bool blockchain_storage::scan_outputkeys_for_indexes(const TransactionInputToKey& tx_in_to_key, visitor_t& vis, uint64_t* pmax_related_block_height) { CRITICAL_REGION_LOCAL(m_blockchain_lock); auto it = m_outputs.find(tx_in_to_key.amount); - if (it == m_outputs.end() || !tx_in_to_key.key_offsets.size()) + if (it == m_outputs.end() || !tx_in_to_key.keyOffsets.size()) return false; - std::vector absolute_offsets = relative_output_offsets_to_absolute(tx_in_to_key.key_offsets); + std::vector absolute_offsets = relative_output_offsets_to_absolute(tx_in_to_key.keyOffsets); std::vector>& amount_outs_vec = it->second; size_t count = 0; for (uint64_t i : absolute_offsets) { @@ -222,7 +291,7 @@ namespace cryptonote { //auto tx_it = m_transactionMap.find(amount_outs_vec[i].first); //CHECK_AND_ASSERT_MES(tx_it != m_transactionMap.end(), false, "Wrong transaction id in output indexes: " << epee::string_tools::pod_to_hex(amount_outs_vec[i].first)); - const Transaction& tx = transactionByIndex(amount_outs_vec[i].first); + const TransactionEntry& tx = transactionByIndex(amount_outs_vec[i].first); CHECK_AND_ASSERT_MES(amount_outs_vec[i].second < tx.tx.vout.size(), false, "Wrong index in transaction outputs: " << amount_outs_vec[i].second << ", expected less then " << tx.tx.vout.size()); if (!vis.handle_output(tx.tx, tx.tx.vout[amount_outs_vec[i].second])) { diff --git a/src/cryptonote_core/checkpoints_create.h b/src/cryptonote_core/checkpoints_create.h deleted file mode 100644 index 5254332a..00000000 --- a/src/cryptonote_core/checkpoints_create.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers -// -// This file is part of Bytecoin. -// -// Bytecoin is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Bytecoin is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with Bytecoin. If not, see . - -#pragma once - -#include "checkpoints.h" -#include "misc_log_ex.h" - -#define ADD_CHECKPOINT(h, hash) CHECK_AND_ASSERT(checkpoints.add_checkpoint(h, hash), false); - -namespace cryptonote { - inline bool create_checkpoints(cryptonote::checkpoints& checkpoints) - { - ADD_CHECKPOINT(79000, "cae33204e624faeb64938d80073bb7bbacc27017dc63f36c5c0f313cad455a02"); - ADD_CHECKPOINT(140000, "993059fb6ab92db7d80d406c67a52d9c02d873ca34b6290a12b744c970208772"); - ADD_CHECKPOINT(200000, "a5f74c7542077df6859f48b5b1f9c3741f29df38f91a47e14c94b5696e6c3073"); - ADD_CHECKPOINT(230580, "32bd7cb6c68a599cf2861941f29002a5e203522b9af54f08dfced316f6459103"); - ADD_CHECKPOINT(260000, "f68e70b360ca194f48084da7a7fd8e0251bbb4b5587f787ca65a6f5baf3f5947"); - ADD_CHECKPOINT(300000, "8e80861713f68354760dc10ea6ea79f5f3ff28f39b3f0835a8637463b09d70ff"); - ADD_CHECKPOINT(390285, "e00bdc9bf407aeace2f3109de11889ed25894bf194231d075eddaec838097eb7"); - ADD_CHECKPOINT(417000, "2dc96f8fc4d4a4d76b3ed06722829a7ab09d310584b8ecedc9b578b2c458a69f"); - ADD_CHECKPOINT(427193, "00feabb08f2d5759ed04fd6b799a7513187478696bba2db2af10d4347134e311"); - ADD_CHECKPOINT(453537, "d17de6916c5aa6ffcae575309c80b0f8fdcd0a84b5fa8e41a841897d4b5a4e97"); - ADD_CHECKPOINT(462250, "13468d210a5ec884cf839f0259f247ccf3efef0414ac45172033d32c739beb3e"); - ADD_CHECKPOINT(468000, "251bcbd398b1f593193a7210934a3d87f692b2cb0c45206150f59683dd7e9ba1"); - ADD_CHECKPOINT(480200, "363544ac9920c778b815c2fdbcbca70a0d79b21f662913a42da9b49e859f0e5b"); - ADD_CHECKPOINT(484500, "5cdf2101a0a62a0ab2a1ca0c15a6212b21f6dbdc42a0b7c0bcf65ca40b7a14fb"); - ADD_CHECKPOINT(506000, "3d54c1132f503d98d3f0d78bb46a4503c1a19447cb348361a2232e241cb45a3c"); - - return true; - } -} diff --git a/src/cryptonote_core/connection_context.h b/src/cryptonote_core/connection_context.h index 0fed9b0c..f18272af 100644 --- a/src/cryptonote_core/connection_context.h +++ b/src/cryptonote_core/connection_context.h @@ -16,11 +16,16 @@ // along with Bytecoin. If not, see . #pragma once -#include + #include +#include +#include + #include "net/net_utils_base.h" #include "copyable_atomic.h" +#include "crypto/hash.h" + namespace cryptonote { diff --git a/src/cryptonote_core/cryptonote_basic.h b/src/cryptonote_core/cryptonote_basic.h index 0f3c7aab..5a02a73c 100644 --- a/src/cryptonote_core/cryptonote_basic.h +++ b/src/cryptonote_core/cryptonote_basic.h @@ -17,344 +17,421 @@ #pragma once -#include -#include +#include #include -#include // memcmp -#include -#include "serialization/serialization.h" -#include "serialization/variant.h" -#include "serialization/vector.h" -#include "serialization/binary_archive.h" -#include "serialization/json_archive.h" -#include "serialization/debug_archive.h" -#include "serialization/crypto.h" -#include "serialization/keyvalue_serialization.h" // eepe named serialization -#include "string_tools.h" -#include "cryptonote_config.h" + +#include +#include + #include "crypto/crypto.h" #include "crypto/hash.h" -#include "misc_language.h" -#include "tx_extra.h" +#include "cryptonote_core/tx_extra.h" +#include "serialization/binary_archive.h" +#include "serialization/crypto.h" +#include "serialization/debug_archive.h" +#include "serialization/json_archive.h" +#include "serialization/serialization.h" +#include "serialization/variant.h" +#include "cryptonote_config.h" +namespace cryptonote { + class account_base; + struct account_keys; + struct Block; + struct Transaction; + struct tx_extra_merge_mining_tag; -namespace cryptonote -{ - - const static crypto::hash null_hash = AUTO_VAL_INIT(null_hash); - const static crypto::public_key null_pkey = AUTO_VAL_INIT(null_pkey); - - typedef std::vector ring_signature; - - - /* outputs */ - - struct txout_to_script - { - std::vector keys; - std::vector script; - - BEGIN_SERIALIZE_OBJECT() - FIELD(keys) - FIELD(script) - END_SERIALIZE() - }; - - struct txout_to_scripthash - { - crypto::hash hash; - }; - - struct txout_to_key - { - txout_to_key() { } - txout_to_key(const crypto::public_key &_key) : key(_key) { } - crypto::public_key key; - }; + // Implemented in cryptonote_format_utils.cpp + bool get_transaction_hash(const Transaction& t, crypto::hash& res); + bool get_mm_tag_from_extra(const std::vector& tx, tx_extra_merge_mining_tag& mm_tag); + const static crypto::hash null_hash = boost::value_initialized(); + const static crypto::public_key null_pkey = boost::value_initialized(); /* inputs */ - struct txin_gen - { + struct TransactionInputGenerate { size_t height; BEGIN_SERIALIZE_OBJECT() - VARINT_FIELD(height) + VARINT_FIELD(height); END_SERIALIZE() }; - struct txin_to_script - { - crypto::hash prev; - size_t prevout; - std::vector sigset; - - BEGIN_SERIALIZE_OBJECT() - FIELD(prev) - VARINT_FIELD(prevout) - FIELD(sigset) - END_SERIALIZE() - }; - - struct txin_to_scripthash - { - crypto::hash prev; - size_t prevout; - txout_to_script script; - std::vector sigset; - - BEGIN_SERIALIZE_OBJECT() - FIELD(prev) - VARINT_FIELD(prevout) - FIELD(script) - FIELD(sigset) - END_SERIALIZE() - }; - - struct txin_to_key - { + struct TransactionInputToKey { uint64_t amount; - std::vector key_offsets; - crypto::key_image k_image; // double spending protection + std::vector keyOffsets; + crypto::key_image keyImage; // double spending protection BEGIN_SERIALIZE_OBJECT() - VARINT_FIELD(amount) - FIELD(key_offsets) - FIELD(k_image) + VARINT_FIELD(amount); + FIELD(keyOffsets); + FIELD(keyImage); END_SERIALIZE() }; - - typedef boost::variant txin_v; - - typedef boost::variant txout_target_v; - - //typedef std::pair out_t; - struct tx_out - { + struct TransactionInputMultisignature { uint64_t amount; - txout_target_v target; + uint32_t signatures; + uint64_t outputIndex; BEGIN_SERIALIZE_OBJECT() - VARINT_FIELD(amount) - FIELD(target) + VARINT_FIELD(amount); + VARINT_FIELD(signatures); + VARINT_FIELD(outputIndex); END_SERIALIZE() - - }; - class transaction_prefix - { + /* outputs */ - public: + struct TransactionOutputToKey { + TransactionOutputToKey() { } + TransactionOutputToKey(const crypto::public_key &_key) : key(_key) { } + crypto::public_key key; + }; + + struct TransactionOutputMultisignature { + std::vector keys; + uint32_t requiredSignatures; + + BEGIN_SERIALIZE_OBJECT() + FIELD(keys); + VARINT_FIELD(requiredSignatures); + END_SERIALIZE() + }; + + struct TransactionInputToScript { + BEGIN_SERIALIZE_OBJECT() + END_SERIALIZE() + }; + + struct TransactionInputToScriptHash { + BEGIN_SERIALIZE_OBJECT() + END_SERIALIZE() + }; + + struct TransactionOutputToScript { + BEGIN_SERIALIZE_OBJECT() + END_SERIALIZE() + }; + + struct TransactionOutputToScriptHash { + BEGIN_SERIALIZE_OBJECT() + END_SERIALIZE() + }; + + typedef boost::variant< + TransactionInputGenerate, + TransactionInputToScript, + TransactionInputToScriptHash, + TransactionInputToKey, + TransactionInputMultisignature> TransactionInput; + + typedef boost::variant< + TransactionOutputToScript, + TransactionOutputToScriptHash, + TransactionOutputToKey, + TransactionOutputMultisignature> TransactionOutputTarget; + + struct TransactionOutput { + uint64_t amount; + TransactionOutputTarget target; + + BEGIN_SERIALIZE_OBJECT() + VARINT_FIELD(amount); + FIELD(target); + END_SERIALIZE() + }; + + struct TransactionPrefix { // tx information size_t version; - uint64_t unlock_time; //number of block (or time), used as a limitation like: spend this tx not early then block/time + uint64_t unlockTime; //number of block (or time), used as a limitation like: spend this tx not early then block/time - std::vector vin; - std::vector vout; + std::vector vin; + std::vector vout; //extra std::vector extra; BEGIN_SERIALIZE() - VARINT_FIELD(version) - if(CURRENT_TRANSACTION_VERSION < version) return false; - VARINT_FIELD(unlock_time) - FIELD(vin) - FIELD(vout) - FIELD(extra) + VARINT_FIELD(version); + if(CURRENT_TRANSACTION_VERSION < version) { + return false; + } + VARINT_FIELD(unlockTime); + FIELD(vin); + FIELD(vout); + FIELD(extra); END_SERIALIZE() - protected: - transaction_prefix(){} + TransactionPrefix() {} }; - class transaction: public transaction_prefix - { - public: + struct Transaction: public TransactionPrefix { std::vector > signatures; //count signatures always the same as inputs count - transaction(); - virtual ~transaction(); - void set_null(); + Transaction() { + clear(); + } + + void clear() { + version = 0; + unlockTime = 0; + vin.clear(); + vout.clear(); + extra.clear(); + signatures.clear(); + } BEGIN_SERIALIZE_OBJECT() - FIELDS(*static_cast(this)) + FIELDS(*static_cast(this)) ar.tag("signatures"); ar.begin_array(); PREPARE_CUSTOM_VECTOR_SERIALIZATION(vin.size(), signatures); bool signatures_not_expected = signatures.empty(); - if (!signatures_not_expected && vin.size() != signatures.size()) + if (!signatures_not_expected && vin.size() != signatures.size()) { return false; + } - for (size_t i = 0; i < vin.size(); ++i) - { - size_t signature_size = get_signature_size(vin[i]); - if (signatures_not_expected) - { - if (0 == signature_size) + for (size_t i = 0; i < vin.size(); ++i) { + size_t signatureSize = getSignatureSize(vin[i]); + if (signatures_not_expected) { + if (0 == signatureSize) { continue; - else + } else { return false; + } } - PREPARE_CUSTOM_VECTOR_SERIALIZATION(signature_size, signatures[i]); - if (signature_size != signatures[i].size()) + PREPARE_CUSTOM_VECTOR_SERIALIZATION(signatureSize, signatures[i]); + if (signatureSize != signatures[i].size()) { return false; + } FIELDS(signatures[i]); - if (vin.size() - i > 1) + if (vin.size() - i > 1) { ar.delimit_array(); + } } ar.end_array(); END_SERIALIZE() private: - static size_t get_signature_size(const txin_v& tx_in); + static size_t getSignatureSize(const TransactionInput& input) { + struct txin_signature_size_visitor : public boost::static_visitor { + size_t operator()(const TransactionInputGenerate& txin) const { return 0; } + size_t operator()(const TransactionInputToScript& txin) const { assert(false); return 0; } + size_t operator()(const TransactionInputToScriptHash& txin) const { assert(false); return 0; } + size_t operator()(const TransactionInputToKey& txin) const { return txin.keyOffsets.size();} + size_t operator()(const TransactionInputMultisignature& txin) const { return txin.signatures; } + }; + + return boost::apply_visitor(txin_signature_size_visitor(), input); + } }; + struct ParentBlock { + uint8_t majorVersion; + uint8_t minorVersion; + crypto::hash prevId; + size_t numberOfTransactions; + std::vector minerTxBranch; + Transaction minerTx; + std::vector blockchainBranch; + }; - inline - transaction::transaction() - { - set_null(); - } + struct ParentBlockSerializer { + ParentBlockSerializer(ParentBlock& parentBlock, uint64_t& timestamp, uint32_t& nonce, bool hashingSerialization, bool headerOnly) : + m_parentBlock(parentBlock), m_timestamp(timestamp), m_nonce(nonce), m_hashingSerialization(hashingSerialization), m_headerOnly(headerOnly) { + } - inline - transaction::~transaction() - { - //set_null(); - } + BEGIN_SERIALIZE_OBJECT() + VARINT_FIELD_N("majorVersion", m_parentBlock.majorVersion); + if (BLOCK_MAJOR_VERSION_1 < m_parentBlock.majorVersion) { + return false; + } + VARINT_FIELD_N("minorVersion", m_parentBlock.minorVersion); + VARINT_FIELD_N("timestamp", m_timestamp); + FIELD_N("prevId", m_parentBlock.prevId); + FIELD_N("nonce", m_nonce); - inline - void transaction::set_null() - { - version = 0; - unlock_time = 0; - vin.clear(); - vout.clear(); - extra.clear(); - signatures.clear(); - } + if (m_hashingSerialization) { + crypto::hash minerTxHash; + if (!get_transaction_hash(m_parentBlock.minerTx, minerTxHash)) { + return false; + } - inline - size_t transaction::get_signature_size(const txin_v& tx_in) - { - struct txin_signature_size_visitor : public boost::static_visitor - { - size_t operator()(const txin_gen& txin) const{return 0;} - size_t operator()(const txin_to_script& txin) const{return 0;} - size_t operator()(const txin_to_scripthash& txin) const{return 0;} - size_t operator()(const txin_to_key& txin) const {return txin.key_offsets.size();} - }; + crypto::hash merkleRoot; + crypto::tree_hash_from_branch(m_parentBlock.minerTxBranch.data(), m_parentBlock.minerTxBranch.size(), minerTxHash, 0, merkleRoot); - return boost::apply_visitor(txin_signature_size_visitor(), tx_in); - } + FIELD(merkleRoot); + } + VARINT_FIELD_N("numberOfTransactions", m_parentBlock.numberOfTransactions); + if (m_parentBlock.numberOfTransactions < 1) { + return false; + } + if (!m_headerOnly) { + ar.tag("minerTxBranch"); + ar.begin_array(); + size_t branchSize = crypto::tree_depth(m_parentBlock.numberOfTransactions); + PREPARE_CUSTOM_VECTOR_SERIALIZATION(branchSize, const_cast(m_parentBlock).minerTxBranch); + if (m_parentBlock.minerTxBranch.size() != branchSize) { + return false; + } + for (size_t i = 0; i < branchSize; ++i) { + FIELDS(m_parentBlock.minerTxBranch[i]); + if (i + 1 < branchSize) { + ar.delimit_array(); + } + } + ar.end_array(); - /************************************************************************/ - /* */ - /************************************************************************/ - struct block_header - { - uint8_t major_version; - uint8_t minor_version; - uint64_t timestamp; - crypto::hash prev_id; + FIELD(m_parentBlock.minerTx); + + tx_extra_merge_mining_tag mmTag; + if (!get_mm_tag_from_extra(m_parentBlock.minerTx.extra, mmTag)) { + return false; + } + + if (mmTag.depth > 8 * sizeof(crypto::hash)) { + return false; + } + + ar.tag("blockchainBranch"); + ar.begin_array(); + PREPARE_CUSTOM_VECTOR_SERIALIZATION(mmTag.depth, const_cast(m_parentBlock).blockchainBranch); + if (mmTag.depth != m_parentBlock.blockchainBranch.size()) { + return false; + } + for (size_t i = 0; i < mmTag.depth; ++i) { + FIELDS(m_parentBlock.blockchainBranch[i]); + if (i + 1 < mmTag.depth) { + ar.delimit_array(); + } + } + ar.end_array(); + } + END_SERIALIZE() + + private: + ParentBlock& m_parentBlock; + uint64_t& m_timestamp; + uint32_t& m_nonce; + bool m_hashingSerialization; + bool m_headerOnly; + }; + + // Implemented below + inline ParentBlockSerializer makeParentBlockSerializer(const Block& b, bool hashingSerialization, bool headerOnly); + + struct BlockHeader { + uint8_t majorVersion; + uint8_t minorVersion; uint32_t nonce; + uint64_t timestamp; + crypto::hash prevId; BEGIN_SERIALIZE() - VARINT_FIELD(major_version) - if(major_version > CURRENT_BLOCK_MAJOR_VERSION) return false; - VARINT_FIELD(minor_version) - VARINT_FIELD(timestamp) - FIELD(prev_id) - FIELD(nonce) + VARINT_FIELD(majorVersion) + if (majorVersion > BLOCK_MAJOR_VERSION_2) { + return false; + } + VARINT_FIELD(minorVersion) + if (majorVersion == BLOCK_MAJOR_VERSION_1) { + VARINT_FIELD(timestamp); + FIELD(prevId); + FIELD(nonce); + } else if (majorVersion == BLOCK_MAJOR_VERSION_2) { + FIELD(prevId); + } else { + return false; + } END_SERIALIZE() }; - struct block: public block_header - { - transaction miner_tx; - std::vector tx_hashes; + struct Block: public BlockHeader { + ParentBlock parentBlock; + + Transaction minerTx; + std::vector txHashes; BEGIN_SERIALIZE_OBJECT() - FIELDS(*static_cast(this)) - FIELD(miner_tx) - FIELD(tx_hashes) + FIELDS(*static_cast(this)); + if (majorVersion == BLOCK_MAJOR_VERSION_2) { + auto serializer = makeParentBlockSerializer(*this, false, false); + FIELD_N("parentBlock", serializer); + } + FIELD(minerTx); + FIELD(txHashes); END_SERIALIZE() }; + inline ParentBlockSerializer makeParentBlockSerializer(const Block& b, bool hashingSerialization, bool headerOnly) { + Block& blockRef = const_cast(b); + return ParentBlockSerializer(blockRef.parentBlock, blockRef.timestamp, blockRef.nonce, hashingSerialization, headerOnly); + } - /************************************************************************/ - /* */ - /************************************************************************/ - struct account_public_address - { - crypto::public_key m_spend_public_key; - crypto::public_key m_view_public_key; + struct AccountPublicAddress { + crypto::public_key m_spendPublicKey; + crypto::public_key m_viewPublicKey; BEGIN_SERIALIZE_OBJECT() - FIELD(m_spend_public_key) - FIELD(m_view_public_key) + FIELD(m_spendPublicKey); + FIELD(m_viewPublicKey); END_SERIALIZE() - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_spend_public_key) - KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_view_public_key) - END_KV_SERIALIZE_MAP() }; - struct keypair - { + struct KeyPair { crypto::public_key pub; crypto::secret_key sec; - static inline keypair generate() - { - keypair k; + static KeyPair generate() { + KeyPair k; generate_keys(k.pub, k.sec); return k; } }; - //--------------------------------------------------------------- - } -BLOB_SERIALIZER(cryptonote::txout_to_key); -BLOB_SERIALIZER(cryptonote::txout_to_scripthash); +BLOB_SERIALIZER(cryptonote::TransactionOutputToKey); -VARIANT_TAG(binary_archive, cryptonote::txin_gen, 0xff); -VARIANT_TAG(binary_archive, cryptonote::txin_to_script, 0x0); -VARIANT_TAG(binary_archive, cryptonote::txin_to_scripthash, 0x1); -VARIANT_TAG(binary_archive, cryptonote::txin_to_key, 0x2); -VARIANT_TAG(binary_archive, cryptonote::txout_to_script, 0x0); -VARIANT_TAG(binary_archive, cryptonote::txout_to_scripthash, 0x1); -VARIANT_TAG(binary_archive, cryptonote::txout_to_key, 0x2); -VARIANT_TAG(binary_archive, cryptonote::transaction, 0xcc); -VARIANT_TAG(binary_archive, cryptonote::block, 0xbb); +VARIANT_TAG(binary_archive, cryptonote::TransactionInputGenerate, 0xff); +VARIANT_TAG(binary_archive, cryptonote::TransactionInputToScript, 0x0); +VARIANT_TAG(binary_archive, cryptonote::TransactionInputToScriptHash, 0x1); +VARIANT_TAG(binary_archive, cryptonote::TransactionInputToKey, 0x2); +VARIANT_TAG(binary_archive, cryptonote::TransactionInputMultisignature, 0x3); +VARIANT_TAG(binary_archive, cryptonote::TransactionOutputToScript, 0x0); +VARIANT_TAG(binary_archive, cryptonote::TransactionOutputToScriptHash, 0x1); +VARIANT_TAG(binary_archive, cryptonote::TransactionOutputToKey, 0x2); +VARIANT_TAG(binary_archive, cryptonote::TransactionOutputMultisignature, 0x3); +VARIANT_TAG(binary_archive, cryptonote::Transaction, 0xcc); +VARIANT_TAG(binary_archive, cryptonote::Block, 0xbb); -VARIANT_TAG(json_archive, cryptonote::txin_gen, "gen"); -VARIANT_TAG(json_archive, cryptonote::txin_to_script, "script"); -VARIANT_TAG(json_archive, cryptonote::txin_to_scripthash, "scripthash"); -VARIANT_TAG(json_archive, cryptonote::txin_to_key, "key"); -VARIANT_TAG(json_archive, cryptonote::txout_to_script, "script"); -VARIANT_TAG(json_archive, cryptonote::txout_to_scripthash, "scripthash"); -VARIANT_TAG(json_archive, cryptonote::txout_to_key, "key"); -VARIANT_TAG(json_archive, cryptonote::transaction, "tx"); -VARIANT_TAG(json_archive, cryptonote::block, "block"); +VARIANT_TAG(json_archive, cryptonote::TransactionInputGenerate, "generate"); +VARIANT_TAG(json_archive, cryptonote::TransactionInputToScript, "script"); +VARIANT_TAG(json_archive, cryptonote::TransactionInputToScriptHash, "scripthash"); +VARIANT_TAG(json_archive, cryptonote::TransactionInputToKey, "key"); +VARIANT_TAG(json_archive, cryptonote::TransactionInputMultisignature, "multisignature"); +VARIANT_TAG(json_archive, cryptonote::TransactionOutputToScript, "script"); +VARIANT_TAG(json_archive, cryptonote::TransactionOutputToScriptHash, "scripthash"); +VARIANT_TAG(json_archive, cryptonote::TransactionOutputToKey, "key"); +VARIANT_TAG(json_archive, cryptonote::TransactionOutputMultisignature, "multisignature"); +VARIANT_TAG(json_archive, cryptonote::Transaction, "Transaction"); +VARIANT_TAG(json_archive, cryptonote::Block, "Block"); -VARIANT_TAG(debug_archive, cryptonote::txin_gen, "gen"); -VARIANT_TAG(debug_archive, cryptonote::txin_to_script, "script"); -VARIANT_TAG(debug_archive, cryptonote::txin_to_scripthash, "scripthash"); -VARIANT_TAG(debug_archive, cryptonote::txin_to_key, "key"); -VARIANT_TAG(debug_archive, cryptonote::txout_to_script, "script"); -VARIANT_TAG(debug_archive, cryptonote::txout_to_scripthash, "scripthash"); -VARIANT_TAG(debug_archive, cryptonote::txout_to_key, "key"); -VARIANT_TAG(debug_archive, cryptonote::transaction, "tx"); -VARIANT_TAG(debug_archive, cryptonote::block, "block"); +VARIANT_TAG(debug_archive, cryptonote::TransactionInputGenerate, "generate"); +VARIANT_TAG(debug_archive, cryptonote::TransactionInputToScript, "script"); +VARIANT_TAG(debug_archive, cryptonote::TransactionInputToScriptHash, "scripthash"); +VARIANT_TAG(debug_archive, cryptonote::TransactionInputToKey, "key"); +VARIANT_TAG(debug_archive, cryptonote::TransactionInputMultisignature, "multisignature"); +VARIANT_TAG(debug_archive, cryptonote::TransactionOutputToScript, "script"); +VARIANT_TAG(debug_archive, cryptonote::TransactionOutputToScriptHash, "scripthash"); +VARIANT_TAG(debug_archive, cryptonote::TransactionOutputToKey, "key"); +VARIANT_TAG(debug_archive, cryptonote::TransactionOutputMultisignature, "multisignature"); +VARIANT_TAG(debug_archive, cryptonote::Transaction, "Transaction"); +VARIANT_TAG(debug_archive, cryptonote::Block, "Block"); diff --git a/src/cryptonote_core/cryptonote_basic_impl.cpp b/src/cryptonote_core/cryptonote_basic_impl.cpp index 5053ea30..b2952a56 100644 --- a/src/cryptonote_core/cryptonote_basic_impl.cpp +++ b/src/cryptonote_core/cryptonote_basic_impl.cpp @@ -35,158 +35,78 @@ namespace cryptonote { /* Cryptonote helper functions */ /************************************************************************/ //----------------------------------------------------------------------------------------------- - size_t get_max_block_size() - { - return CRYPTONOTE_MAX_BLOCK_SIZE; - } - //----------------------------------------------------------------------------------------------- - size_t get_max_tx_size() - { - return CRYPTONOTE_MAX_TX_SIZE; - } - //----------------------------------------------------------------------------------------------- - bool get_block_reward(size_t median_size, size_t current_block_size, uint64_t already_generated_coins, uint64_t &reward) { - uint64_t base_reward = (MONEY_SUPPLY - already_generated_coins) >> EMISSION_SPEED_FACTOR; + uint64_t getPenalizedAmount(uint64_t amount, size_t medianSize, size_t currentBlockSize) { + static_assert(sizeof(size_t) >= sizeof(uint32_t), "size_t is too small"); + assert(currentBlockSize <= 2 * medianSize); + assert(medianSize <= std::numeric_limits::max()); + assert(currentBlockSize <= std::numeric_limits::max()); - //make it soft - if (median_size < CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE) { - median_size = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE; + if (amount == 0) { + return 0; } - if (current_block_size <= median_size) { - reward = base_reward; - return true; + if (currentBlockSize <= medianSize) { + return amount; } - if(current_block_size > 2 * median_size) { - LOG_PRINT_L4("Block cumulative size is too big: " << current_block_size << ", expected less than " << 2 * median_size); + uint64_t productHi; + uint64_t productLo = mul128(amount, currentBlockSize * (UINT64_C(2) * medianSize - currentBlockSize), &productHi); + + uint64_t penalizedAmountHi; + uint64_t penalizedAmountLo; + div128_32(productHi, productLo, static_cast(medianSize), &penalizedAmountHi, &penalizedAmountLo); + div128_32(penalizedAmountHi, penalizedAmountLo, static_cast(medianSize), &penalizedAmountHi, &penalizedAmountLo); + + assert(0 == penalizedAmountHi); + assert(penalizedAmountLo < amount); + + return penalizedAmountLo; + } + //----------------------------------------------------------------------- + std::string getAccountAddressAsStr(uint64_t prefix, const AccountPublicAddress& adr) { + blobdata blob; + bool r = t_serializable_object_to_blob(adr, blob); + assert(r); + return tools::base58::encode_addr(prefix, blob); + } + //----------------------------------------------------------------------- + bool is_coinbase(const Transaction& tx) { + if(tx.vin.size() != 1) { return false; } - assert(median_size < std::numeric_limits::max()); - assert(current_block_size < std::numeric_limits::max()); - - uint64_t product_hi; - uint64_t product_lo = mul128(base_reward, current_block_size * (2 * median_size - current_block_size), &product_hi); - - uint64_t reward_hi; - uint64_t reward_lo; - div128_32(product_hi, product_lo, static_cast(median_size), &reward_hi, &reward_lo); - div128_32(reward_hi, reward_lo, static_cast(median_size), &reward_hi, &reward_lo); - assert(0 == reward_hi); - assert(reward_lo < base_reward); - - reward = reward_lo; - return true; - } - //------------------------------------------------------------------------------------ - uint8_t get_account_address_checksum(const public_address_outer_blob& bl) - { - const unsigned char* pbuf = reinterpret_cast(&bl); - uint8_t summ = 0; - for(size_t i = 0; i!= sizeof(public_address_outer_blob)-1; i++) - summ += pbuf[i]; - - return summ; - } - //----------------------------------------------------------------------- - std::string get_account_address_as_str(const account_public_address& adr) - { - return tools::base58::encode_addr(CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, t_serializable_object_to_blob(adr)); - } - //----------------------------------------------------------------------- - bool is_coinbase(const transaction& tx) - { - if(tx.vin.size() != 1) - return false; - - if(tx.vin[0].type() != typeid(txin_gen)) - return false; - - return true; - } - //----------------------------------------------------------------------- - bool get_account_address_from_str(uint64_t& prefix, account_public_address& adr, const std::string& str) - { - if (2 * sizeof(public_address_outer_blob) != str.size()) - { - blobdata data; - if (!tools::base58::decode_addr(str, prefix, data)) - { - LOG_PRINT_L1("Invalid address format"); - return false; - } - - if (!::serialization::parse_binary(data, adr)) - { - LOG_PRINT_L1("Account public address keys can't be parsed"); - return false; - } - - if (!crypto::check_key(adr.m_spend_public_key) || !crypto::check_key(adr.m_view_public_key)) - { - LOG_PRINT_L1("Failed to validate address keys"); - return false; - } - } - else - { - // Old address format - prefix = CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX; - - std::string buff; - if(!string_tools::parse_hexstr_to_binbuff(str, buff)) - return false; - - if(buff.size()!=sizeof(public_address_outer_blob)) - { - LOG_PRINT_L1("Wrong public address size: " << buff.size() << ", expected size: " << sizeof(public_address_outer_blob)); - return false; - } - - public_address_outer_blob blob = *reinterpret_cast(buff.data()); - - - if(blob.m_ver > CRYPTONOTE_PUBLIC_ADDRESS_TEXTBLOB_VER) - { - LOG_PRINT_L1("Unknown version of public address: " << blob.m_ver << ", expected " << CRYPTONOTE_PUBLIC_ADDRESS_TEXTBLOB_VER); - return false; - } - - if(blob.check_sum != get_account_address_checksum(blob)) - { - LOG_PRINT_L1("Wrong public address checksum"); - return false; - } - - //we success - adr = blob.m_address; - } - - return true; - } - //----------------------------------------------------------------------- - bool get_account_address_from_str(account_public_address& adr, const std::string& str) - { - uint64_t prefix; - if(!get_account_address_from_str(prefix, adr, str)) - return false; - - if(CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX != prefix) - { - LOG_PRINT_L1("Wrong address prefix: " << prefix << ", expected " << CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX); + if(tx.vin[0].type() != typeid(TransactionInputGenerate)) { return false; } return true; } //----------------------------------------------------------------------- + bool parseAccountAddressString(uint64_t& prefix, AccountPublicAddress& adr, const std::string& str) { + blobdata data; + if (!tools::base58::decode_addr(str, prefix, data)) { + LOG_PRINT_L1("Invalid address format"); + return false; + } - bool operator ==(const cryptonote::transaction& a, const cryptonote::transaction& b) { + if (!::serialization::parse_binary(data, adr)) { + LOG_PRINT_L1("Account public address keys can't be parsed"); + return false; + } + + if (!crypto::check_key(adr.m_spendPublicKey) || !crypto::check_key(adr.m_viewPublicKey)) { + LOG_PRINT_L1("Failed to validate address keys"); + return false; + } + + return true; + } + //----------------------------------------------------------------------- + bool operator ==(const cryptonote::Transaction& a, const cryptonote::Transaction& b) { return cryptonote::get_transaction_hash(a) == cryptonote::get_transaction_hash(b); } - - bool operator ==(const cryptonote::block& a, const cryptonote::block& b) { + //----------------------------------------------------------------------- + bool operator ==(const cryptonote::Block& a, const cryptonote::Block& b) { return cryptonote::get_block_hash(a) == cryptonote::get_block_hash(b); } } diff --git a/src/cryptonote_core/cryptonote_basic_impl.h b/src/cryptonote_core/cryptonote_basic_impl.h index ae3dd747..793df885 100644 --- a/src/cryptonote_core/cryptonote_basic_impl.h +++ b/src/cryptonote_core/cryptonote_basic_impl.h @@ -17,9 +17,12 @@ #pragma once -#include "cryptonote_basic.h" +//epee +#include "string_tools.h" + #include "crypto/crypto.h" #include "crypto/hash.h" +#include "cryptonote_core/cryptonote_basic.h" namespace cryptonote { @@ -35,31 +38,16 @@ namespace cryptonote { } }; - -#pragma pack(push, 1) - struct public_address_outer_blob - { - uint8_t m_ver; - account_public_address m_address; - uint8_t check_sum; - }; -#pragma pack (pop) - - /************************************************************************/ /* Cryptonote helper functions */ /************************************************************************/ - size_t get_max_block_size(); - size_t get_max_tx_size(); - bool get_block_reward(size_t median_size, size_t current_block_size, uint64_t already_generated_coins, uint64_t &reward); - uint8_t get_account_address_checksum(const public_address_outer_blob& bl); - std::string get_account_address_as_str(const account_public_address& adr); - bool get_account_address_from_str(uint64_t& prefix, account_public_address& adr, const std::string& str); - bool get_account_address_from_str(account_public_address& adr, const std::string& str); - bool is_coinbase(const transaction& tx); + uint64_t getPenalizedAmount(uint64_t amount, size_t medianSize, size_t currentBlockSize); + std::string getAccountAddressAsStr(uint64_t prefix, const AccountPublicAddress& adr); + bool parseAccountAddressString(uint64_t& prefix, AccountPublicAddress& adr, const std::string& str); + bool is_coinbase(const Transaction& tx); - bool operator ==(const cryptonote::transaction& a, const cryptonote::transaction& b); - bool operator ==(const cryptonote::block& a, const cryptonote::block& b); + bool operator ==(const cryptonote::Transaction& a, const cryptonote::Transaction& b); + bool operator ==(const cryptonote::Block& a, const cryptonote::Block& b); } template diff --git a/src/cryptonote_core/cryptonote_boost_serialization.h b/src/cryptonote_core/cryptonote_boost_serialization.h index 53847498..d1a7b5d7 100644 --- a/src/cryptonote_core/cryptonote_boost_serialization.h +++ b/src/cryptonote_core/cryptonote_boost_serialization.h @@ -67,59 +67,55 @@ namespace boost a & reinterpret_cast(x); } - template - inline void serialize(Archive &a, cryptonote::txout_to_script &x, const boost::serialization::version_type ver) - { - a & x.keys; - a & x.script; + template void serialize(Archive& archive, cryptonote::TransactionInputToScript&, unsigned int version) { + assert(false); } + template void serialize(Archive& archive, cryptonote::TransactionInputToScriptHash&, unsigned int version) { + assert(false); + } + + template void serialize(Archive& archive, cryptonote::TransactionOutputToScript&, unsigned int version) { + assert(false); + } + + template void serialize(Archive& archive, cryptonote::TransactionOutputToScriptHash&, unsigned int version) { + assert(false); + } + + template void serialize(Archive& archive, cryptonote::TransactionInputMultisignature &output, unsigned int version) { + archive & output.amount; + archive & output.signatures; + archive & output.outputIndex; + } + + template void serialize(Archive& archive, cryptonote::TransactionOutputMultisignature &output, unsigned int version) { + archive & output.keys; + archive & output.requiredSignatures; + } template - inline void serialize(Archive &a, cryptonote::txout_to_key &x, const boost::serialization::version_type ver) + inline void serialize(Archive &a, cryptonote::TransactionOutputToKey &x, const boost::serialization::version_type ver) { a & x.key; } template - inline void serialize(Archive &a, cryptonote::txout_to_scripthash &x, const boost::serialization::version_type ver) - { - a & x.hash; - } - - template - inline void serialize(Archive &a, cryptonote::txin_gen &x, const boost::serialization::version_type ver) + inline void serialize(Archive &a, cryptonote::TransactionInputGenerate &x, const boost::serialization::version_type ver) { a & x.height; } template - inline void serialize(Archive &a, cryptonote::txin_to_script &x, const boost::serialization::version_type ver) - { - a & x.prev; - a & x.prevout; - a & x.sigset; - } - - template - inline void serialize(Archive &a, cryptonote::txin_to_scripthash &x, const boost::serialization::version_type ver) - { - a & x.prev; - a & x.prevout; - a & x.script; - a & x.sigset; - } - - template - inline void serialize(Archive &a, cryptonote::txin_to_key &x, const boost::serialization::version_type ver) + inline void serialize(Archive &a, cryptonote::TransactionInputToKey &x, const boost::serialization::version_type ver) { a & x.amount; - a & x.key_offsets; - a & x.k_image; + a & x.keyOffsets; + a & x.keyImage; } template - inline void serialize(Archive &a, cryptonote::tx_out &x, const boost::serialization::version_type ver) + inline void serialize(Archive &a, cryptonote::TransactionOutput &x, const boost::serialization::version_type ver) { a & x.amount; a & x.target; @@ -127,10 +123,10 @@ namespace boost template - inline void serialize(Archive &a, cryptonote::transaction &x, const boost::serialization::version_type ver) + inline void serialize(Archive &a, cryptonote::Transaction &x, const boost::serialization::version_type ver) { a & x.version; - a & x.unlock_time; + a & x.unlockTime; a & x.vin; a & x.vout; a & x.extra; @@ -139,16 +135,16 @@ namespace boost template - inline void serialize(Archive &a, cryptonote::block &b, const boost::serialization::version_type ver) + inline void serialize(Archive &a, cryptonote::Block &b, const boost::serialization::version_type ver) { - a & b.major_version; - a & b.minor_version; + a & b.majorVersion; + a & b.minorVersion; a & b.timestamp; - a & b.prev_id; + a & b.prevId; a & b.nonce; //------------------ - a & b.miner_tx; - a & b.tx_hashes; + a & b.minerTx; + a & b.txHashes; } } } diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 004df14a..ca4f1f96 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -15,19 +15,29 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . -#include "include_base_utils.h" -using namespace epee; - -#include -#include #include "cryptonote_core.h" + +#include +#include + +#include "storages/portable_storage_template_helper.h" +#include "include_base_utils.h" +#include "misc_log_ex.h" +#include "misc_language.h" +#include "warnings.h" + #include "common/command_line.h" #include "common/util.h" -#include "warnings.h" #include "crypto/crypto.h" +#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_core/cryptonote_stat_info.h" +#include "cryptonote_core/miner.h" #include "cryptonote_config.h" -#include "cryptonote_format_utils.h" -#include "misc_language.h" +#include "cryptonote_protocol/cryptonote_protocol_defs.h" +#include "rpc/core_rpc_server_commands_defs.h" + + +using namespace epee; DISABLE_VS_WARNINGS(4355) @@ -35,15 +45,19 @@ namespace cryptonote { //----------------------------------------------------------------------------------------------- - core::core(i_cryptonote_protocol* pprotocol): - m_mempool(m_blockchain_storage), - m_blockchain_storage(m_mempool), - m_miner(this), - m_miner_address(boost::value_initialized()), + core::core(const Currency& currency, i_cryptonote_protocol* pprotocol): + m_currency(currency), + m_mempool(currency, m_blockchain_storage, m_timeProvider), + m_blockchain_storage(currency, m_mempool), + m_miner(new miner(currency, this)), m_starter_message_showed(false) { set_cryptonote_protocol(pprotocol); } + //----------------------------------------------------------------------------------------------- + core::~core() { + } + //----------------------------------------------------------------------------------------------- void core::set_cryptonote_protocol(i_cryptonote_protocol* pprotocol) { if(pprotocol) @@ -78,21 +92,21 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- - bool core::get_blocks(uint64_t start_offset, size_t count, std::list& blocks, std::list& txs) + bool core::get_blocks(uint64_t start_offset, size_t count, std::list& blocks, std::list& txs) { return m_blockchain_storage.get_blocks(start_offset, count, blocks, txs); } //----------------------------------------------------------------------------------------------- - bool core::get_blocks(uint64_t start_offset, size_t count, std::list& blocks) + bool core::get_blocks(uint64_t start_offset, size_t count, std::list& blocks) { return m_blockchain_storage.get_blocks(start_offset, count, blocks); } //----------------------------------------------------------------------------------------------- - void core::get_transactions(const std::vector& txs_ids, std::list& txs, std::list& missed_txs) + void core::get_transactions(const std::vector& txs_ids, std::list& txs, std::list& missed_txs) { m_blockchain_storage.get_transactions(txs_ids, txs, missed_txs); } //----------------------------------------------------------------------------------------------- - bool core::get_alternative_blocks(std::list& blocks) + bool core::get_alternative_blocks(std::list& blocks) { return m_blockchain_storage.get_alternative_blocks(blocks); } @@ -112,13 +126,13 @@ namespace cryptonote r = m_blockchain_storage.init(m_config_folder, load_existing); CHECK_AND_ASSERT_MES(r, false, "Failed to initialize blockchain storage"); - r = m_miner.init(vm); + r = m_miner->init(vm); CHECK_AND_ASSERT_MES(r, false, "Failed to initialize blockchain storage"); return load_state_data(); } //----------------------------------------------------------------------------------------------- - bool core::set_genesis_block(const block& b) + bool core::set_genesis_block(const Block& b) { return m_blockchain_storage.reset_and_set_genesis_block(b); } @@ -131,7 +145,7 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- bool core::deinit() { - m_miner.stop(); + m_miner->stop(); m_mempool.deinit(); m_blockchain_storage.deinit(); return true; @@ -143,7 +157,7 @@ namespace cryptonote //want to process all transactions sequentially CRITICAL_REGION_LOCAL(m_incoming_tx_lock); - if(tx_blob.size() > get_max_tx_size()) + if(tx_blob.size() > m_currency.maxTxSize()) { LOG_PRINT_L0("WRONG TRANSACTION BLOB, too big size " << tx_blob.size() << ", rejected"); tvc.m_verifivation_failed = true; @@ -152,7 +166,7 @@ namespace cryptonote crypto::hash tx_hash = null_hash; crypto::hash tx_prefixt_hash = null_hash; - transaction tx; + Transaction tx; if(!parse_tx_from_blob(tx, tx_hash, tx_prefixt_hash, tx_blob)) { @@ -177,19 +191,26 @@ namespace cryptonote } bool r = add_new_tx(tx, tx_hash, tx_prefixt_hash, tx_blob.size(), tvc, keeped_by_block); - if(tvc.m_verifivation_failed) - {LOG_PRINT_RED_L0("Transaction verification failed: " << tx_hash);} - else if(tvc.m_verifivation_impossible) - {LOG_PRINT_RED_L0("Transaction verification impossible: " << tx_hash);} + if(tvc.m_verifivation_failed) { + if (!tvc.m_tx_fee_too_small) { + LOG_PRINT_RED_L0("Transaction verification failed: " << tx_hash); + } else { + LOG_PRINT_L0("Transaction verification failed: " << tx_hash); + } + } else if(tvc.m_verifivation_impossible) { + LOG_PRINT_RED_L0("Transaction verification impossible: " << tx_hash); + } - if(tvc.m_added_to_pool) + if (tvc.m_added_to_pool) { LOG_PRINT_L1("tx added: " << tx_hash); + } + return r; } //----------------------------------------------------------------------------------------------- bool core::get_stat_info(core_stat_info& st_inf) { - st_inf.mining_speed = m_miner.get_speed(); + st_inf.mining_speed = m_miner->get_speed(); st_inf.alternative_blocks = m_blockchain_storage.get_alternative_blocks_count(); st_inf.blockchain_height = m_blockchain_storage.get_current_blockchain_height(); st_inf.tx_pool_size = m_mempool.get_transactions_count(); @@ -198,7 +219,7 @@ namespace cryptonote } //----------------------------------------------------------------------------------------------- - bool core::check_tx_semantic(const transaction& tx, bool keeped_by_block) + bool core::check_tx_semantic(const Transaction& tx, bool keeped_by_block) { if(!tx.vin.size()) { @@ -234,36 +255,41 @@ namespace cryptonote return false; } - if(!keeped_by_block && get_object_blobsize(tx) >= m_blockchain_storage.get_current_comulative_blocksize_limit() - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE) + if(!keeped_by_block && get_object_blobsize(tx) >= m_blockchain_storage.get_current_comulative_blocksize_limit() - m_currency.minerTxBlobReservedSize()) { - LOG_PRINT_RED_L0("tx have to big size " << get_object_blobsize(tx) << ", expected not bigger than " << m_blockchain_storage.get_current_comulative_blocksize_limit() - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE); + LOG_PRINT_RED_L0("tx have to big size " << get_object_blobsize(tx) << ", expected not bigger than " << + (m_blockchain_storage.get_current_comulative_blocksize_limit() - m_currency.minerTxBlobReservedSize())); return false; } //check if tx use different key images if(!check_tx_inputs_keyimages_diff(tx)) { - LOG_PRINT_RED_L0("tx have to big size " << get_object_blobsize(tx) << ", expected not bigger than " << m_blockchain_storage.get_current_comulative_blocksize_limit() - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE); + LOG_PRINT_RED_L0("tx has a few inputs with identical keyimages"); return false; } + if (!checkMultisignatureInputsDiff(tx)) { + LOG_PRINT_RED_L0("tx has a few multisignature inputs with identical output indexes"); + return false; + } return true; } //----------------------------------------------------------------------------------------------- - bool core::check_tx_inputs_keyimages_diff(const transaction& tx) + bool core::check_tx_inputs_keyimages_diff(const Transaction& tx) { std::unordered_set ki; - BOOST_FOREACH(const auto& in, tx.vin) - { - CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); - if(!ki.insert(tokey_in.k_image).second) - return false; + for (const auto& in : tx.vin) { + if (in.type() == typeid(TransactionInputToKey)) { + if (!ki.insert(boost::get(in).keyImage).second) + return false; + } } return true; } //----------------------------------------------------------------------------------------------- - bool core::add_new_tx(const transaction& tx, tx_verification_context& tvc, bool keeped_by_block) + bool core::add_new_tx(const Transaction& tx, tx_verification_context& tvc, bool keeped_by_block) { crypto::hash tx_hash = get_transaction_hash(tx); crypto::hash tx_prefix_hash = get_transaction_prefix_hash(tx); @@ -282,7 +308,7 @@ namespace cryptonote // return m_blockchain_storage.get_outs(amount, pkeys); //} //----------------------------------------------------------------------------------------------- - bool core::add_new_tx(const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block) { + bool core::add_new_tx(const Transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block) { if (m_blockchain_storage.have_tx(tx_hash)) { LOG_PRINT_L2("tx " << tx_hash << " is already in blockchain"); return true; @@ -299,7 +325,7 @@ namespace cryptonote return m_mempool.add_tx(tx, tx_hash, blob_size, tvc, keeped_by_block); } //----------------------------------------------------------------------------------------------- - bool core::get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce) + bool core::get_block_template(Block& b, const AccountPublicAddress& adr, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce) { return m_blockchain_storage.create_block_template(b, adr, diffic, height, ex_nonce); } @@ -309,7 +335,7 @@ namespace cryptonote return m_blockchain_storage.find_blockchain_supplement(qblock_ids, resp); } //----------------------------------------------------------------------------------------------- - bool core::find_blockchain_supplement(const std::list& qblock_ids, std::list > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) + bool core::find_blockchain_supplement(const std::list& qblock_ids, std::list > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) { return m_blockchain_storage.find_blockchain_supplement(qblock_ids, blocks, total_height, start_height, max_count); } @@ -339,109 +365,85 @@ namespace cryptonote return m_blockchain_storage.get_tx_outputs_gindexs(tx_id, indexs); } //----------------------------------------------------------------------------------------------- - void core::pause_mine() - { - m_miner.pause(); + void core::pause_mining() { + m_miner->pause(); } //----------------------------------------------------------------------------------------------- - void core::resume_mine() - { - m_miner.resume(); - } - //----------------------------------------------------------------------------------------------- - bool core::handle_block_found(block& b) - { - block_verification_context bvc = boost::value_initialized(); - m_miner.pause(); - m_blockchain_storage.add_new_block(b, bvc); - //anyway - update miner template + void core::update_block_template_and_resume_mining() { update_miner_block_template(); - m_miner.resume(); + m_miner->resume(); + } + //----------------------------------------------------------------------------------------------- + bool core::handle_block_found(Block& b) { + block_verification_context bvc = boost::value_initialized(); + handle_incoming_block(b, bvc, true, true); - - CHECK_AND_ASSERT_MES(!bvc.m_verifivation_failed, false, "mined block failed verification"); - if(bvc.m_added_to_main_chain) - { - cryptonote_connection_context exclude_context = boost::value_initialized(); - NOTIFY_NEW_BLOCK::request arg = AUTO_VAL_INIT(arg); - arg.hop = 0; - arg.current_blockchain_height = m_blockchain_storage.get_current_blockchain_height(); - std::list missed_txs; - std::list txs; - m_blockchain_storage.get_transactions(b.tx_hashes, txs, missed_txs); - if(missed_txs.size() && m_blockchain_storage.get_block_id_by_height(get_block_height(b)) != get_block_hash(b)) - { - LOG_PRINT_L0("Block found but, seems that reorganize just happened after that, do not relay this block"); - return true; - } - CHECK_AND_ASSERT_MES(txs.size() == b.tx_hashes.size() && !missed_txs.size(), false, "cant find some transactions in found block:" << get_block_hash(b) << " txs.size()=" << txs.size() - << ", b.tx_hashes.size()=" << b.tx_hashes.size() << ", missed_txs.size()" << missed_txs.size()); - - block_to_blob(b, arg.b.block); - //pack transactions - BOOST_FOREACH(auto& tx, txs) - arg.b.txs.push_back(t_serializable_object_to_blob(tx)); - - m_pprotocol->relay_block(arg, exclude_context); + if (bvc.m_verifivation_failed) { + LOG_ERROR("mined block failed verification"); } + return bvc.m_added_to_main_chain; } //----------------------------------------------------------------------------------------------- void core::on_synchronized() { - m_miner.on_synchronized(); + m_miner->on_synchronized(); } - //bool core::get_backward_blocks_sizes(uint64_t from_height, std::vector& sizes, size_t count) - //{ - // return m_blockchain_storage.get_backward_blocks_sizes(from_height, sizes, count); - //} //----------------------------------------------------------------------------------------------- - bool core::handle_incoming_block(const blobdata& block_blob, block_verification_context& bvc, bool update_miner_blocktemplate) - { - bvc = boost::value_initialized(); - if(block_blob.size() > get_max_block_size()) - { + bool core::handle_incoming_block_blob(const blobdata& block_blob, block_verification_context& bvc, bool control_miner, bool relay_block) { + if (block_blob.size() > m_currency.maxBlockBlobSize()) { LOG_PRINT_L0("WRONG BLOCK BLOB, too big size " << block_blob.size() << ", rejected"); bvc.m_verifivation_failed = true; return false; } - block b = AUTO_VAL_INIT(b); - if(!parse_and_validate_block_from_blob(block_blob, b)) - { + Block b = AUTO_VAL_INIT(b); + if (!parse_and_validate_block_from_blob(block_blob, b)) { LOG_PRINT_L0("Failed to parse and validate new block"); bvc.m_verifivation_failed = true; return false; } + return handle_incoming_block(b, bvc, control_miner, relay_block); + } + //----------------------------------------------------------------------------------------------- + bool core::handle_incoming_block(const Block& b, block_verification_context& bvc, bool control_miner, bool relay_block) { + if (control_miner) { + pause_mining(); + } + m_blockchain_storage.add_new_block(b, bvc); - if (bvc.m_added_to_main_chain && update_miner_blocktemplate) { - update_miner_block_template(); + + if (control_miner) { + update_block_template_and_resume_mining(); + } + + if (relay_block && bvc.m_added_to_main_chain) { + std::list missed_txs; + std::list txs; + m_blockchain_storage.get_transactions(b.txHashes, txs, missed_txs); + if (!missed_txs.empty() && m_blockchain_storage.get_block_id_by_height(get_block_height(b)) != get_block_hash(b)) { + LOG_PRINT_L0("Block added, but it seems that reorganize just happened after that, do not relay this block"); + } else { + CHECK_AND_ASSERT_MES(txs.size() == b.txHashes.size() && missed_txs.empty(), false, "can't find some transactions in found block:" << + get_block_hash(b) << " txs.size()=" << txs.size() << ", b.txHashes.size()=" << b.txHashes.size() << ", missed_txs.size()" << missed_txs.size()); + + NOTIFY_NEW_BLOCK::request arg = AUTO_VAL_INIT(arg); + arg.hop = 0; + arg.current_blockchain_height = m_blockchain_storage.get_current_blockchain_height(); + bool r = block_to_blob(b, arg.b.block); + CHECK_AND_ASSERT_MES(r, false, "failed to serialize block"); + for (auto& tx : txs) { + arg.b.txs.push_back(t_serializable_object_to_blob(tx)); + } + + cryptonote_connection_context exclude_context = boost::value_initialized(); + m_pprotocol->relay_block(arg, exclude_context); + } } return true; } - - void core::notify_new_block(const block& b) { - cryptonote_connection_context exclude_context = boost::value_initialized(); - NOTIFY_NEW_BLOCK::request arg = AUTO_VAL_INIT(arg); - arg.hop = 0; - arg.current_blockchain_height = m_blockchain_storage.get_current_blockchain_height(); - std::list missed_txs; - std::list txs; - m_blockchain_storage.get_transactions(b.tx_hashes, txs, missed_txs); - if (missed_txs.size() > 0 && m_blockchain_storage.get_block_id_by_height(get_block_height(b)) != get_block_hash(b)) { - LOG_PRINT_L0("Block found but reorganize happened after that, block will not be relayed"); - } else { - if (txs.size() != b.tx_hashes.size() || missed_txs.size()) { - LOG_ERROR("cant find some transactions in found block:" << get_block_hash(b) << " txs.size()=" << txs.size() << ", b.tx_hashes.size()=" << b.tx_hashes.size() << ", missed_txs.size()" << missed_txs.size()); - return; - } - block_to_blob(b, arg.b.block); - BOOST_FOREACH(auto& tx, txs) arg.b.txs.push_back(t_serializable_object_to_blob(tx)); - m_pprotocol->relay_block(arg, exclude_context); - } - } //----------------------------------------------------------------------------------------------- crypto::hash core::get_tail_id() { @@ -458,17 +460,17 @@ namespace cryptonote return m_blockchain_storage.have_block(id); } //----------------------------------------------------------------------------------------------- - bool core::parse_tx_from_blob(transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash, const blobdata& blob) + bool core::parse_tx_from_blob(Transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash, const blobdata& blob) { return parse_and_validate_tx_from_blob(blob, tx, tx_hash, tx_prefix_hash); } //----------------------------------------------------------------------------------------------- - bool core::check_tx_syntax(const transaction& tx) + bool core::check_tx_syntax(const Transaction& tx) { return true; } //----------------------------------------------------------------------------------------------- - void core::get_pool_transactions(std::list& txs) + void core::get_pool_transactions(std::list& txs) { m_mempool.get_transactions(txs); } @@ -488,7 +490,7 @@ namespace cryptonote return m_blockchain_storage.get_block_id_by_height(height); } //----------------------------------------------------------------------------------------------- - bool core::get_block_by_hash(const crypto::hash &h, block &blk) { + bool core::get_block_by_hash(const crypto::hash &h, Block &blk) { return m_blockchain_storage.get_block_by_hash(h, blk); } //----------------------------------------------------------------------------------------------- @@ -503,7 +505,7 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- bool core::update_miner_block_template() { - m_miner.on_block_chain_update(); + m_miner->on_block_chain_update(); return true; } //----------------------------------------------------------------------------------------------- @@ -523,8 +525,8 @@ namespace cryptonote m_starter_message_showed = true; } - m_store_blockchain_interval.do_call(boost::bind(&blockchain_storage::store_blockchain, &m_blockchain_storage)); - m_miner.on_idle(); + m_miner->on_idle(); + m_mempool.on_idle(); return true; } //----------------------------------------------------------------------------------------------- diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 5c79f6da..25969086 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -22,78 +22,77 @@ #include "p2p/net_node_common.h" #include "cryptonote_protocol/cryptonote_protocol_handler_common.h" -#include "storages/portable_storage_template_helper.h" +#include "Currency.h" #include "tx_pool.h" #include "blockchain_storage.h" -#include "miner.h" +#include "cryptonote_core/i_miner_handler.h" #include "connection_context.h" -#include "cryptonote_core/cryptonote_stat_info.h" #include "warnings.h" #include "crypto/hash.h" PUSH_WARNINGS DISABLE_VS_WARNINGS(4355) -namespace cryptonote -{ - /************************************************************************/ - /* */ - /************************************************************************/ - class core: public i_miner_handler - { +namespace cryptonote { + struct core_stat_info; + class miner; + + class core: public i_miner_handler { public: - core(i_cryptonote_protocol* pprotocol); - bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp, cryptonote_connection_context& context); + core(const Currency& currency, i_cryptonote_protocol* pprotocol); + ~core(); + bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS_request& arg, NOTIFY_RESPONSE_GET_OBJECTS_request& rsp, cryptonote_connection_context& context); bool on_idle(); bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block); - bool handle_incoming_block(const blobdata& block_blob, block_verification_context& bvc, bool update_miner_blocktemplate = true); + bool handle_incoming_block_blob(const blobdata& block_blob, block_verification_context& bvc, bool control_miner, bool relay_block); + const Currency& currency() const { return m_currency; } i_cryptonote_protocol* get_protocol(){return m_pprotocol;} //-------------------- i_miner_handler ----------------------- - virtual bool handle_block_found( block& b); - virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce); + virtual bool handle_block_found(Block& b); + virtual bool get_block_template(Block& b, const AccountPublicAddress& adr, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce); - miner& get_miner(){return m_miner;} + miner& get_miner() { return *m_miner; } static void init_options(boost::program_options::options_description& desc); bool init(const boost::program_options::variables_map& vm, bool load_existing); - bool set_genesis_block(const block& b); + bool set_genesis_block(const Block& b); bool deinit(); uint64_t get_current_blockchain_height(); bool get_blockchain_top(uint64_t& heeight, crypto::hash& top_id); - bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks, std::list& txs); - bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks); + bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks, std::list& txs); + bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks); template bool get_blocks(const t_ids_container& block_ids, t_blocks_container& blocks, t_missed_container& missed_bs) { return m_blockchain_storage.get_blocks(block_ids, blocks, missed_bs); } crypto::hash get_block_id_by_height(uint64_t height); - void get_transactions(const std::vector& txs_ids, std::list& txs, std::list& missed_txs); - bool get_block_by_hash(const crypto::hash &h, block &blk); + void get_transactions(const std::vector& txs_ids, std::list& txs, std::list& missed_txs); + bool get_block_by_hash(const crypto::hash &h, Block &blk); //void get_all_known_block_ids(std::list &main, std::list &alt, std::list &invalid); - bool get_alternative_blocks(std::list& blocks); + bool get_alternative_blocks(std::list& blocks); size_t get_alternative_blocks_count(); void set_cryptonote_protocol(i_cryptonote_protocol* pprotocol); void set_checkpoints(checkpoints&& chk_pts); - void get_pool_transactions(std::list& txs); + void get_pool_transactions(std::list& txs); size_t get_pool_transactions_count(); size_t get_blockchain_total_transactions(); //bool get_outs(uint64_t amount, std::list& pkeys); bool have_block(const crypto::hash& id); bool get_short_chain_history(std::list& ids); - bool find_blockchain_supplement(const std::list& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp); - bool find_blockchain_supplement(const std::list& qblock_ids, std::list > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count); + bool find_blockchain_supplement(const std::list& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY_request& resp); + bool find_blockchain_supplement(const std::list& qblock_ids, std::list > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count); bool get_stat_info(core_stat_info& st_inf); //bool get_backward_blocks_sizes(uint64_t from_height, std::vector& sizes, size_t count); bool get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector& indexs); crypto::hash get_tail_id(); - bool get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res); - void pause_mine(); - void resume_mine(); + bool get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_response& res); + void pause_mining(); + void update_block_template_and_resume_mining(); blockchain_storage& get_blockchain_storage(){return m_blockchain_storage;} //debug functions void print_blockchain(uint64_t start_index, uint64_t end_index); @@ -101,39 +100,37 @@ namespace cryptonote std::string print_pool(bool short_format); void print_blockchain_outs(const std::string& file); void on_synchronized(); - void notify_new_block(const block& b); private: - bool add_new_tx(const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block); - bool add_new_tx(const transaction& tx, tx_verification_context& tvc, bool keeped_by_block); + bool add_new_tx(const Transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block); + bool add_new_tx(const Transaction& tx, tx_verification_context& tvc, bool keeped_by_block); bool load_state_data(); - bool parse_tx_from_blob(transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash, const blobdata& blob); + bool parse_tx_from_blob(Transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash, const blobdata& blob); + bool handle_incoming_block(const Block& b, block_verification_context& bvc, bool control_miner, bool relay_block); - bool check_tx_syntax(const transaction& tx); + bool check_tx_syntax(const Transaction& tx); //check correct values, amounts and all lightweight checks not related with database - bool check_tx_semantic(const transaction& tx, bool keeped_by_block); + bool check_tx_semantic(const Transaction& tx, bool keeped_by_block); //check if tx already in memory pool or in main blockchain bool is_key_image_spent(const crypto::key_image& key_im); - bool check_tx_ring_signature(const txin_to_key& tx, const crypto::hash& tx_prefix_hash, const std::vector& sig); + bool check_tx_ring_signature(const TransactionInputToKey& tx, const crypto::hash& tx_prefix_hash, const std::vector& sig); bool is_tx_spendtime_unlocked(uint64_t unlock_time); bool update_miner_block_template(); bool handle_command_line(const boost::program_options::variables_map& vm); bool on_update_blocktemplate_interval(); - bool check_tx_inputs_keyimages_diff(const transaction& tx); - + bool check_tx_inputs_keyimages_diff(const Transaction& tx); + const Currency& m_currency; + CryptoNote::RealTimeProvider m_timeProvider; tx_memory_pool m_mempool; blockchain_storage m_blockchain_storage; i_cryptonote_protocol* m_pprotocol; epee::critical_section m_incoming_tx_lock; - //m_miner and m_miner_addres are probably temporary here - miner m_miner; - account_public_address m_miner_address; + std::unique_ptr m_miner; std::string m_config_folder; cryptonote_protocol_stub m_protocol_stub; - epee::math_helper::once_a_time_seconds<60*60*12, false> m_store_blockchain_interval; friend class tx_validate_inputs; std::atomic m_starter_message_showed; }; diff --git a/src/cryptonote_core/cryptonote_format_utils.cpp b/src/cryptonote_core/cryptonote_format_utils.cpp index 9de9e141..16bcdb7e 100644 --- a/src/cryptonote_core/cryptonote_format_utils.cpp +++ b/src/cryptonote_core/cryptonote_format_utils.cpp @@ -15,35 +15,40 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . -#include "include_base_utils.h" -using namespace epee; +#include + +// epee +#include "include_base_utils.h" +#include "misc_language.h" -#include "cryptonote_format_utils.h" -#include -#include "cryptonote_config.h" -#include "miner.h" #include "crypto/crypto.h" #include "crypto/hash.h" +#include "cryptonote_core/account.h" +#include "cryptonote_core/cryptonote_format_utils.h" +#include "serialization/binary_utils.h" +#include "cryptonote_config.h" + +using namespace epee; namespace cryptonote { //--------------------------------------------------------------- - void get_transaction_prefix_hash(const transaction_prefix& tx, crypto::hash& h) + void get_transaction_prefix_hash(const TransactionPrefix& tx, crypto::hash& h) { std::ostringstream s; binary_archive a(s); - ::serialization::serialize(a, const_cast(tx)); + ::serialization::serialize(a, const_cast(tx)); crypto::cn_fast_hash(s.str().data(), s.str().size(), h); } //--------------------------------------------------------------- - crypto::hash get_transaction_prefix_hash(const transaction_prefix& tx) + crypto::hash get_transaction_prefix_hash(const TransactionPrefix& tx) { crypto::hash h = null_hash; get_transaction_prefix_hash(tx, h); return h; } //--------------------------------------------------------------- - bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx) + bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, Transaction& tx) { std::stringstream ss; ss << tx_blob; @@ -53,7 +58,7 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash) + bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, Transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash) { std::stringstream ss; ss << tx_blob; @@ -67,83 +72,14 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool construct_miner_tx(size_t height, size_t median_size, uint64_t already_generated_coins, size_t current_block_size, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce, size_t max_outs) { - tx.vin.clear(); - tx.vout.clear(); - tx.extra.clear(); - - keypair txkey = keypair::generate(); - add_tx_pub_key_to_extra(tx, txkey.pub); - if(!extra_nonce.empty()) - if(!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) - return false; - - txin_gen in; - in.height = height; - - uint64_t block_reward; - if(!get_block_reward(median_size, current_block_size, already_generated_coins, block_reward)) - { - LOG_PRINT_L0("Block is too big"); - return false; - } -#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) - LOG_PRINT_L1("Creating block template: reward " << block_reward << - ", fee " << fee) -#endif - block_reward += fee; - - std::vector out_amounts; - decompose_amount_into_digits(block_reward, DEFAULT_DUST_THRESHOLD, - [&out_amounts](uint64_t a_chunk) { out_amounts.push_back(a_chunk); }, - [&out_amounts](uint64_t a_dust) { out_amounts.push_back(a_dust); }); - - CHECK_AND_ASSERT_MES(1 <= max_outs, false, "max_out must be non-zero"); - while (max_outs < out_amounts.size()) - { - out_amounts[out_amounts.size() - 2] += out_amounts.back(); - out_amounts.resize(out_amounts.size() - 1); - } - - uint64_t summary_amounts = 0; - for (size_t no = 0; no < out_amounts.size(); no++) - { - crypto::key_derivation derivation = AUTO_VAL_INIT(derivation);; - crypto::public_key out_eph_public_key = AUTO_VAL_INIT(out_eph_public_key); - bool r = crypto::generate_key_derivation(miner_address.m_view_public_key, txkey.sec, derivation); - CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to generate_key_derivation(" << miner_address.m_view_public_key << ", " << txkey.sec << ")"); - - r = crypto::derive_public_key(derivation, no, miner_address.m_spend_public_key, out_eph_public_key); - CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to derive_public_key(" << derivation << ", " << no << ", "<< miner_address.m_spend_public_key << ")"); - - txout_to_key tk; - tk.key = out_eph_public_key; - - tx_out out; - summary_amounts += out.amount = out_amounts[no]; - out.target = tk; - tx.vout.push_back(out); - } - - CHECK_AND_ASSERT_MES(summary_amounts == block_reward, false, "Failed to construct miner tx, summary_amounts = " << summary_amounts << " not equal block_reward = " << block_reward); - - tx.version = CURRENT_TRANSACTION_VERSION; - //lock - tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; - tx.vin.push_back(in); - //LOG_PRINT("MINER_TX generated ok, block_reward=" << print_money(block_reward) << "(" << print_money(block_reward - fee) << "+" << print_money(fee) - // << "), current_block_size=" << current_block_size << ", already_generated_coins=" << already_generated_coins << ", tx_id=" << get_transaction_hash(tx), LOG_LEVEL_2); - return true; - } - //--------------------------------------------------------------- - bool generate_key_image_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki) + bool generate_key_image_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, KeyPair& in_ephemeral, crypto::key_image& ki) { crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, recv_derivation); CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); - r = crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub); - CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to derive_public_key(" << recv_derivation << ", " << real_output_index << ", " << ack.m_account_address.m_spend_public_key << ")"); + r = crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spendPublicKey, in_ephemeral.pub); + CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to derive_public_key(" << recv_derivation << ", " << real_output_index << ", " << ack.m_account_address.m_spendPublicKey << ")"); crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, in_ephemeral.sec); @@ -161,59 +97,29 @@ namespace cryptonote return total; } //--------------------------------------------------------------- - bool parse_amount(uint64_t& amount, const std::string& str_amount_) - { - std::string str_amount = str_amount_; - boost::algorithm::trim(str_amount); - - size_t point_index = str_amount.find_first_of('.'); - size_t fraction_size; - if (std::string::npos != point_index) - { - fraction_size = str_amount.size() - point_index - 1; - while (CRYPTONOTE_DISPLAY_DECIMAL_POINT < fraction_size && '0' == str_amount.back()) - { - str_amount.erase(str_amount.size() - 1, 1); - --fraction_size; - } - if (CRYPTONOTE_DISPLAY_DECIMAL_POINT < fraction_size) - return false; - str_amount.erase(point_index, 1); - } - else - { - fraction_size = 0; - } - - if (str_amount.empty()) - return false; - - if (fraction_size < CRYPTONOTE_DISPLAY_DECIMAL_POINT) - { - str_amount.append(CRYPTONOTE_DISPLAY_DECIMAL_POINT - fraction_size, '0'); - } - - return string_tools::get_xtype_from_string(amount, str_amount); - } - //--------------------------------------------------------------- - bool get_tx_fee(const transaction& tx, uint64_t & fee) + bool get_tx_fee(const Transaction& tx, uint64_t & fee) { uint64_t amount_in = 0; uint64_t amount_out = 0; - BOOST_FOREACH(auto& in, tx.vin) - { - CHECK_AND_ASSERT_MES(in.type() == typeid(txin_to_key), 0, "unexpected type id in transaction"); - amount_in += boost::get(in).amount; + + for (const auto& in : tx.vin) { + if (in.type() == typeid(TransactionInputToKey)) { + amount_in += boost::get(in).amount; + } else if (in.type() == typeid(TransactionInputMultisignature)) { + amount_in += boost::get(in).amount; + } } - BOOST_FOREACH(auto& o, tx.vout) + + for (const auto& o : tx.vout) { amount_out += o.amount; + } CHECK_AND_ASSERT_MES(amount_in >= amount_out, false, "transaction spend (" < ar(iss); bool eof = false; - while (!eof) - { + while (!eof) { tx_extra_field field; bool r = ::do_serialize(ar, field); - CHECK_AND_NO_ASSERT_MES(r, false, "failed to deserialize extra field. extra = " << string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast(tx_extra.data()), tx_extra.size()))); + if (!r) { + LOG_PRINT_L4("failed to deserialize extra field. extra = " << + string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast(tx_extra.data()), tx_extra.size()))); + return false; + } tx_extra_fields.push_back(field); std::ios_base::iostate state = iss.rdstate(); eof = (EOF == iss.peek()); iss.clear(state); } - CHECK_AND_NO_ASSERT_MES(::serialization::check_stream_state(ar), false, "failed to deserialize extra field. extra = " << string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast(tx_extra.data()), tx_extra.size()))); + + if (!::serialization::check_stream_state(ar)) { + LOG_PRINT_L4("failed to deserialize extra field. extra = " << + string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast(tx_extra.data()), tx_extra.size()))); + return false; + } return true; } @@ -261,12 +175,12 @@ namespace cryptonote return pub_key_field.pub_key; } //--------------------------------------------------------------- - crypto::public_key get_tx_pub_key_from_extra(const transaction& tx) + crypto::public_key get_tx_pub_key_from_extra(const Transaction& tx) { return get_tx_pub_key_from_extra(tx.extra); } //--------------------------------------------------------------- - bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key) + bool add_tx_pub_key_to_extra(Transaction& tx, const crypto::public_key& tx_pub_key) { tx.extra.resize(tx.extra.size() + 1 + sizeof(crypto::public_key)); tx.extra[tx.extra.size() - 1 - sizeof(crypto::public_key)] = TX_EXTRA_TAG_PUBKEY; @@ -290,6 +204,24 @@ namespace cryptonote return true; } //--------------------------------------------------------------- + bool append_mm_tag_to_extra(std::vector& tx_extra, const tx_extra_merge_mining_tag& mm_tag) { + blobdata blob; + if (!t_serializable_object_to_blob(mm_tag, blob)) { + return false; + } + + tx_extra.push_back(TX_EXTRA_MERGE_MINING_TAG); + std::copy(reinterpret_cast(blob.data()), reinterpret_cast(blob.data() + blob.size()), std::back_inserter(tx_extra)); + return true; + } + //--------------------------------------------------------------- + bool get_mm_tag_from_extra(const std::vector& tx_extra, tx_extra_merge_mining_tag& mm_tag) { + std::vector tx_extra_fields; + parse_tx_extra(tx_extra, tx_extra_fields); + + return find_tx_extra_field_by_type(tx_extra_fields, mm_tag); + } + //--------------------------------------------------------------- void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id) { extra_nonce.clear(); @@ -308,29 +240,29 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool construct_tx(const account_keys& sender_account_keys, const std::vector& sources, const std::vector& destinations, std::vector extra, transaction& tx, uint64_t unlock_time) + bool construct_tx(const account_keys& sender_account_keys, const std::vector& sources, const std::vector& destinations, std::vector extra, Transaction& tx, uint64_t unlock_time) { tx.vin.clear(); tx.vout.clear(); tx.signatures.clear(); tx.version = CURRENT_TRANSACTION_VERSION; - tx.unlock_time = unlock_time; + tx.unlockTime = unlock_time; tx.extra = extra; - keypair txkey = keypair::generate(); + KeyPair txkey = KeyPair::generate(); add_tx_pub_key_to_extra(tx, txkey.pub); struct input_generation_context_data { - keypair in_ephemeral; + KeyPair in_ephemeral; }; std::vector in_contexts; uint64_t summary_inputs_money = 0; //fill inputs - BOOST_FOREACH(const tx_source_entry& src_entr, sources) + for (const tx_source_entry& src_entr : sources) { if(src_entr.real_output >= src_entr.outputs.size()) { @@ -341,7 +273,7 @@ namespace cryptonote //key_derivation recv_derivation; in_contexts.push_back(input_generation_context_data()); - keypair& in_ephemeral = in_contexts.back().in_ephemeral; + KeyPair& in_ephemeral = in_contexts.back().in_ephemeral; crypto::key_image img; if(!generate_key_image_helper(sender_account_keys, src_entr.real_out_tx_key, src_entr.real_output_in_tx_index, in_ephemeral, img)) return false; @@ -356,15 +288,16 @@ namespace cryptonote } //put key image into tx input - txin_to_key input_to_key; + TransactionInputToKey input_to_key; input_to_key.amount = src_entr.amount; - input_to_key.k_image = img; + input_to_key.keyImage = img; //fill outputs array and use relative offsets - BOOST_FOREACH(const tx_source_entry::output_entry& out_entry, src_entr.outputs) - input_to_key.key_offsets.push_back(out_entry.first); + for (const tx_source_entry::output_entry& out_entry : src_entr.outputs) { + input_to_key.keyOffsets.push_back(out_entry.first); + } - input_to_key.key_offsets = absolute_output_offsets_to_relative(input_to_key.key_offsets); + input_to_key.keyOffsets = absolute_output_offsets_to_relative(input_to_key.keyOffsets); tx.vin.push_back(input_to_key); } @@ -375,20 +308,19 @@ namespace cryptonote uint64_t summary_outs_money = 0; //fill outputs size_t output_index = 0; - BOOST_FOREACH(const tx_destination_entry& dst_entr, shuffled_dsts) - { + for (const tx_destination_entry& dst_entr : shuffled_dsts) { CHECK_AND_ASSERT_MES(dst_entr.amount > 0, false, "Destination with wrong amount: " << dst_entr.amount); crypto::key_derivation derivation; crypto::public_key out_eph_public_key; - bool r = crypto::generate_key_derivation(dst_entr.addr.m_view_public_key, txkey.sec, derivation); - CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << dst_entr.addr.m_view_public_key << ", " << txkey.sec << ")"); + bool r = crypto::generate_key_derivation(dst_entr.addr.m_viewPublicKey, txkey.sec, derivation); + CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << dst_entr.addr.m_viewPublicKey << ", " << txkey.sec << ")"); - r = crypto::derive_public_key(derivation, output_index, dst_entr.addr.m_spend_public_key, out_eph_public_key); - CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to derive_public_key(" << derivation << ", " << output_index << ", "<< dst_entr.addr.m_spend_public_key << ")"); + r = crypto::derive_public_key(derivation, output_index, dst_entr.addr.m_spendPublicKey, out_eph_public_key); + CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to derive_public_key(" << derivation << ", " << output_index << ", "<< dst_entr.addr.m_spendPublicKey << ")"); - tx_out out; + TransactionOutput out; out.amount = dst_entr.amount; - txout_to_key tk; + TransactionOutputToKey tk; tk.key = out_eph_public_key; out.target = tk; tx.vout.push_back(out); @@ -410,12 +342,10 @@ namespace cryptonote std::stringstream ss_ring_s; size_t i = 0; - BOOST_FOREACH(const tx_source_entry& src_entr, sources) - { + for (const tx_source_entry& src_entr : sources) { ss_ring_s << "pub_keys:" << ENDL; std::vector keys_ptrs; - BOOST_FOREACH(const tx_source_entry::output_entry& o, src_entr.outputs) - { + for (const tx_source_entry::output_entry& o : src_entr.outputs) { keys_ptrs.push_back(&o.second); ss_ring_s << o.second << ENDL; } @@ -423,10 +353,12 @@ namespace cryptonote tx.signatures.push_back(std::vector()); std::vector& sigs = tx.signatures.back(); sigs.resize(src_entr.outputs.size()); - crypto::generate_ring_signature(tx_prefix_hash, boost::get(tx.vin[i]).k_image, keys_ptrs, in_contexts[i].in_ephemeral.sec, src_entr.real_output, sigs.data()); + crypto::generate_ring_signature(tx_prefix_hash, boost::get(tx.vin[i]).keyImage, keys_ptrs, + in_contexts[i].in_ephemeral.sec, src_entr.real_output, sigs.data()); ss_ring_s << "signatures:" << ENDL; std::for_each(sigs.begin(), sigs.end(), [&](const crypto::signature& s){ss_ring_s << s << ENDL;}); - ss_ring_s << "prefix_hash:" << tx_prefix_hash << ENDL << "in_ephemeral_key: " << in_contexts[i].in_ephemeral.sec << ENDL << "real_output: " << src_entr.real_output; + ss_ring_s << "prefix_hash:" << tx_prefix_hash << ENDL << "in_ephemeral_key: " << in_contexts[i].in_ephemeral.sec << + ENDL << "real_output: " << src_entr.real_output; i++; } @@ -435,75 +367,118 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool get_inputs_money_amount(const transaction& tx, uint64_t& money) + bool get_inputs_money_amount(const Transaction& tx, uint64_t& money) { money = 0; - BOOST_FOREACH(const auto& in, tx.vin) - { - CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); - money += tokey_in.amount; + + for (const auto& in : tx.vin) { + uint64_t amount = 0; + + if (in.type() == typeid(TransactionInputToKey)) { + amount = boost::get(in).amount; + } else if (in.type() == typeid(TransactionInputMultisignature)) { + amount = boost::get(in).amount; + } + + money += amount; } return true; } //--------------------------------------------------------------- - uint64_t get_block_height(const block& b) + uint64_t get_block_height(const Block& b) { - CHECK_AND_ASSERT_MES(b.miner_tx.vin.size() == 1, 0, "wrong miner tx in block: " << get_block_hash(b) << ", b.miner_tx.vin.size() != 1"); - CHECKED_GET_SPECIFIC_VARIANT(b.miner_tx.vin[0], const txin_gen, coinbase_in, 0); + CHECK_AND_ASSERT_MES(b.minerTx.vin.size() == 1, 0, "wrong miner tx in block: " << get_block_hash(b) << ", b.minerTx.vin.size() != 1"); + CHECKED_GET_SPECIFIC_VARIANT(b.minerTx.vin[0], const TransactionInputGenerate, coinbase_in, 0); return coinbase_in.height; } //--------------------------------------------------------------- - bool check_inputs_types_supported(const transaction& tx) - { - BOOST_FOREACH(const auto& in, tx.vin) - { - CHECK_AND_ASSERT_MES(in.type() == typeid(txin_to_key), false, "wrong variant type: " - << in.type().name() << ", expected " << typeid(txin_to_key).name() - << ", in transaction id=" << get_transaction_hash(tx)); - - } - return true; - } - //----------------------------------------------------------------------------------------------- - bool check_outs_valid(const transaction& tx) - { - BOOST_FOREACH(const tx_out& out, tx.vout) - { - CHECK_AND_ASSERT_MES(out.target.type() == typeid(txout_to_key), false, "wrong variant type: " - << out.target.type().name() << ", expected " << typeid(txout_to_key).name() - << ", in transaction id=" << get_transaction_hash(tx)); - - CHECK_AND_NO_ASSERT_MES(0 < out.amount, false, "zero amount ouput in transaction id=" << get_transaction_hash(tx)); - - if(!check_key(boost::get(out.target).key)) + bool check_inputs_types_supported(const Transaction& tx) { + for (const auto& in : tx.vin) { + if (in.type() != typeid(TransactionInputToKey) && in.type() != typeid(TransactionInputMultisignature)) { + LOG_PRINT_L1("Transaction << " << get_transaction_hash(tx) << " contains inputs with invalid type."); return false; + } } + return true; } //----------------------------------------------------------------------------------------------- - bool check_money_overflow(const transaction& tx) + bool check_outs_valid(const Transaction& tx) { + for (const TransactionOutput& out : tx.vout) { + //assert(out.target.type() == typeid(TransactionOutputToKey) || out.target.type() == typeid(TransactionOutputMultisignature)); + if (out.target.type() == typeid(TransactionOutputToKey)) { + CHECK_AND_NO_ASSERT_MES(0 < out.amount, false, "zero amount ouput in transaction id=" << get_transaction_hash(tx)); + + if (!check_key(boost::get(out.target).key)) { + return false; + } + } else if (out.target.type() == typeid(TransactionOutputMultisignature)) { + const TransactionOutputMultisignature& multisignatureOutput = ::boost::get(out.target); + if (multisignatureOutput.requiredSignatures > multisignatureOutput.keys.size()) { + LOG_PRINT_L1("Transaction << " << get_transaction_hash(tx) << " contains multisignature output with invalid required signature count."); + return false; + } + + for (const crypto::public_key& key : multisignatureOutput.keys) { + if (!check_key(key)) { + LOG_PRINT_L1("Transaction << " << get_transaction_hash(tx) << " contains multisignature output with invalid public keys."); + return false; + } + } + } else { + LOG_PRINT_L1("Transaction << " << get_transaction_hash(tx) << " contains outputs with invalid type."); + return false; + } + } + + return true; + } + + //----------------------------------------------------------------------------------------------- + bool checkMultisignatureInputsDiff(const Transaction& tx) { + std::set> inputsUsage; + for (const auto& inv : tx.vin) { + if (inv.type() == typeid(TransactionInputMultisignature)) { + const TransactionInputMultisignature& in = ::boost::get(inv); + if (!inputsUsage.insert(std::make_pair(in.amount, static_cast(in.outputIndex))).second) { + return false; + } + } + } + return true; + } + + //----------------------------------------------------------------------------------------------- + bool check_money_overflow(const Transaction& tx) { return check_inputs_overflow(tx) && check_outs_overflow(tx); } //--------------------------------------------------------------- - bool check_inputs_overflow(const transaction& tx) + bool check_inputs_overflow(const Transaction& tx) { uint64_t money = 0; - BOOST_FOREACH(const auto& in, tx.vin) - { - CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); - if(money > tokey_in.amount + money) + + for (const auto& in : tx.vin) { + uint64_t amount = 0; + + if (in.type() == typeid(TransactionInputToKey)) { + amount = boost::get(in).amount; + } else if (in.type() == typeid(TransactionInputMultisignature)) { + amount = boost::get(in).amount; + } + + if (money > amount + money) return false; - money += tokey_in.amount; + + money += amount; } return true; } //--------------------------------------------------------------- - bool check_outs_overflow(const transaction& tx) + bool check_outs_overflow(const Transaction& tx) { uint64_t money = 0; - BOOST_FOREACH(const auto& o, tx.vout) - { + for (const auto& o : tx.vout) { if(money > o.amount + money) return false; money += o.amount; @@ -511,11 +486,12 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - uint64_t get_outs_money_amount(const transaction& tx) + uint64_t get_outs_money_amount(const Transaction& tx) { uint64_t outputs_amount = 0; - BOOST_FOREACH(const auto& o, tx.vout) + for (const auto& o : tx.vout) { outputs_amount += o.amount; + } return outputs_amount; } //--------------------------------------------------------------- @@ -527,17 +503,25 @@ namespace cryptonote res.insert(8, "...."); return res; } + //--------------------------------------------------------------- - bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, size_t output_index) + bool is_out_to_acc(const account_keys& acc, const TransactionOutputToKey& out_key, const crypto::key_derivation& derivation, size_t keyIndex) + { + crypto::public_key pk; + derive_public_key(derivation, keyIndex, acc.m_account_address.m_spendPublicKey, pk); + return pk == out_key.key; + } + + //--------------------------------------------------------------- + bool is_out_to_acc(const account_keys& acc, const TransactionOutputToKey& out_key, const crypto::public_key& tx_pub_key, size_t keyIndex) { crypto::key_derivation derivation; generate_key_derivation(tx_pub_key, acc.m_view_secret_key, derivation); - crypto::public_key pk; - derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk); - return pk == out_key.key; + return is_out_to_acc(acc, out_key, derivation, keyIndex); } + //--------------------------------------------------------------- - bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector& outs, uint64_t& money_transfered) + bool lookup_acc_outs(const account_keys& acc, const Transaction& tx, std::vector& outs, uint64_t& money_transfered) { crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); if(null_pkey == tx_pub_key) @@ -545,19 +529,29 @@ namespace cryptonote return lookup_acc_outs(acc, tx, tx_pub_key, outs, money_transfered); } //--------------------------------------------------------------- - bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector& outs, uint64_t& money_transfered) + bool lookup_acc_outs(const account_keys& acc, const Transaction& tx, const crypto::public_key& tx_pub_key, std::vector& outs, uint64_t& money_transfered) { money_transfered = 0; - size_t i = 0; - BOOST_FOREACH(const tx_out& o, tx.vout) - { - CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_key), false, "wrong type id in transaction out" ); - if(is_out_to_acc(acc, boost::get(o.target), tx_pub_key, i)) - { - outs.push_back(i); - money_transfered += o.amount; + size_t keyIndex = 0; + size_t outputIndex = 0; + + crypto::key_derivation derivation; + generate_key_derivation(tx_pub_key, acc.m_view_secret_key, derivation); + + for (const TransactionOutput& o : tx.vout) { + assert(o.target.type() == typeid(TransactionOutputToKey) || o.target.type() == typeid(TransactionOutputMultisignature)); + if (o.target.type() == typeid(TransactionOutputToKey)) { + if (is_out_to_acc(acc, boost::get(o.target), derivation, keyIndex)) { + outs.push_back(outputIndex); + money_transfered += o.amount; + } + + ++keyIndex; + } else if (o.target.type() == typeid(TransactionOutputMultisignature)) { + keyIndex += boost::get(o.target).keys.size(); } - i++; + + ++outputIndex; } return true; } @@ -567,17 +561,6 @@ namespace cryptonote cn_fast_hash(blob.data(), blob.size(), res); } //--------------------------------------------------------------- - std::string print_money(uint64_t amount) - { - std::string s = std::to_string(amount); - if(s.size() < CRYPTONOTE_DISPLAY_DECIMAL_POINT+1) - { - s.insert(0, CRYPTONOTE_DISPLAY_DECIMAL_POINT+1 - s.size(), '0'); - } - s.insert(s.size() - CRYPTONOTE_DISPLAY_DECIMAL_POINT, "."); - return s; - } - //--------------------------------------------------------------- crypto::hash get_blob_hash(const blobdata& blob) { crypto::hash h = null_hash; @@ -585,7 +568,7 @@ namespace cryptonote return h; } //--------------------------------------------------------------- - crypto::hash get_transaction_hash(const transaction& t) + crypto::hash get_transaction_hash(const Transaction& t) { crypto::hash h = null_hash; size_t blob_size = 0; @@ -593,75 +576,79 @@ namespace cryptonote return h; } //--------------------------------------------------------------- - bool get_transaction_hash(const transaction& t, crypto::hash& res) + bool get_transaction_hash(const Transaction& t, crypto::hash& res) { size_t blob_size = 0; return get_object_hash(t, res, blob_size); } //--------------------------------------------------------------- - bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t& blob_size) + bool get_transaction_hash(const Transaction& t, crypto::hash& res, size_t& blob_size) { return get_object_hash(t, res, blob_size); } //--------------------------------------------------------------- - bool get_block_hashing_blob(const block& b, blobdata& blob) - { - if(!t_serializable_object_to_blob(static_cast(b), blob)) + bool get_block_hashing_blob(const Block& b, blobdata& blob) { + if (!t_serializable_object_to_blob(static_cast(b), blob)) { return false; + } crypto::hash tree_root_hash = get_tx_tree_hash(b); blob.append(reinterpret_cast(&tree_root_hash), sizeof(tree_root_hash)); - blob.append(tools::get_varint_data(b.tx_hashes.size() + 1)); + blob.append(tools::get_varint_data(b.txHashes.size() + 1)); return true; } //--------------------------------------------------------------- - bool get_block_hash(const block& b, crypto::hash& res) - { + bool get_parent_block_hashing_blob(const Block& b, blobdata& blob) { + auto serializer = makeParentBlockSerializer(b, true, true); + return t_serializable_object_to_blob(serializer, blob); + } + //--------------------------------------------------------------- + bool get_block_hash(const Block& b, crypto::hash& res) { blobdata blob; - if (!get_block_hashing_blob(b, blob)) + if (!get_block_hashing_blob(b, blob)) { return false; + } + + if (BLOCK_MAJOR_VERSION_2 <= b.majorVersion) { + blobdata parent_blob; + auto serializer = makeParentBlockSerializer(b, true, false); + if (!t_serializable_object_to_blob(serializer, parent_blob)) + return false; + + blob.append(parent_blob); + } + return get_object_hash(blob, res); } //--------------------------------------------------------------- - crypto::hash get_block_hash(const block& b) - { + crypto::hash get_block_hash(const Block& b) { crypto::hash p = null_hash; get_block_hash(b, p); return p; } //--------------------------------------------------------------- - bool generate_genesis_block(block& bl) - { - //genesis block - bl = boost::value_initialized(); + bool get_aux_block_header_hash(const Block& b, crypto::hash& res) { + blobdata blob; + if (!get_block_hashing_blob(b, blob)) { + return false; + } - - account_public_address ac = boost::value_initialized(); - std::vector sz; - construct_miner_tx(0, 0, 0, 0, 0, ac, bl.miner_tx); // zero fee in genesis - blobdata txb = tx_to_blob(bl.miner_tx); - std::string hex_tx_represent = string_tools::buff_to_hex_nodelimer(txb); - - //hard code coinbase tx in genesis block, because "tru" generating tx use random, but genesis should be always the same - std::string genesis_coinbase_tx_hex = "010a01ff0001ffffffffffff0f029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121013c086a48c15fb637a96991bc6d53caf77068b5ba6eeb3c82357228c49790584a"; - - blobdata tx_bl; - string_tools::parse_hexstr_to_binbuff(genesis_coinbase_tx_hex, tx_bl); - bool r = parse_and_validate_tx_from_blob(tx_bl, bl.miner_tx); - CHECK_AND_ASSERT_MES(r, false, "failed to parse coinbase tx from hard coded blob"); - bl.major_version = CURRENT_BLOCK_MAJOR_VERSION; - bl.minor_version = CURRENT_BLOCK_MINOR_VERSION; - bl.timestamp = 0; - bl.nonce = 70; - //miner::find_nonce_for_given_block(bl, 1, 0); - return true; + return get_object_hash(blob, res); } //--------------------------------------------------------------- - bool get_block_longhash(crypto::cn_context &context, const block& b, crypto::hash& res, uint64_t height) - { + bool get_block_longhash(crypto::cn_context &context, const Block& b, crypto::hash& res) { blobdata bd; - if(!get_block_hashing_blob(b, bd)) + if (b.majorVersion == BLOCK_MAJOR_VERSION_1) { + if (!get_block_hashing_blob(b, bd)) { + return false; + } + } else if (b.majorVersion == BLOCK_MAJOR_VERSION_2) { + if (!get_parent_block_hashing_blob(b, bd)) { + return false; + } + } else { return false; + } crypto::cn_slow_hash(context, bd.data(), bd.size(), res); return true; } @@ -686,14 +673,7 @@ namespace cryptonote return res; } //--------------------------------------------------------------- - crypto::hash get_block_longhash(crypto::cn_context &context, const block& b, uint64_t height) - { - crypto::hash p = null_hash; - get_block_longhash(context, b, p, height); - return p; - } - //--------------------------------------------------------------- - bool parse_and_validate_block_from_blob(const blobdata& b_blob, block& b) + bool parse_and_validate_block_from_blob(const blobdata& b_blob, Block& b) { std::stringstream ss; ss << b_blob; @@ -703,22 +683,22 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - blobdata block_to_blob(const block& b) + blobdata block_to_blob(const Block& b) { return t_serializable_object_to_blob(b); } //--------------------------------------------------------------- - bool block_to_blob(const block& b, blobdata& b_blob) + bool block_to_blob(const Block& b, blobdata& b_blob) { return t_serializable_object_to_blob(b, b_blob); } //--------------------------------------------------------------- - blobdata tx_to_blob(const transaction& tx) + blobdata tx_to_blob(const Transaction& tx) { return t_serializable_object_to_blob(tx); } //--------------------------------------------------------------- - bool tx_to_blob(const transaction& tx, blobdata& b_blob) + bool tx_to_blob(const Transaction& tx, blobdata& b_blob) { return t_serializable_object_to_blob(tx, b_blob); } @@ -735,15 +715,16 @@ namespace cryptonote return h; } //--------------------------------------------------------------- - crypto::hash get_tx_tree_hash(const block& b) + crypto::hash get_tx_tree_hash(const Block& b) { std::vector txs_ids; crypto::hash h = null_hash; size_t bl_sz = 0; - get_transaction_hash(b.miner_tx, h, bl_sz); + get_transaction_hash(b.minerTx, h, bl_sz); txs_ids.push_back(h); - BOOST_FOREACH(auto& th, b.tx_hashes) + for (auto& th : b.txHashes) { txs_ids.push_back(th); + } return get_tx_tree_hash(txs_ids); } //--------------------------------------------------------------- diff --git a/src/cryptonote_core/cryptonote_format_utils.h b/src/cryptonote_core/cryptonote_format_utils.h index 3ff408a7..af588bf8 100644 --- a/src/cryptonote_core/cryptonote_format_utils.h +++ b/src/cryptonote_core/cryptonote_format_utils.h @@ -16,22 +16,27 @@ // along with Bytecoin. If not, see . #pragma once -#include "cryptonote_protocol/cryptonote_protocol_defs.h" -#include "cryptonote_core/cryptonote_basic_impl.h" -#include "account.h" + +#include + +#include + #include "include_base_utils.h" + #include "crypto/crypto.h" #include "crypto/hash.h" +#include "cryptonote_core/cryptonote_basic_impl.h" +#include "cryptonote_core/difficulty.h" +#include "cryptonote_protocol/blobdatatype.h" namespace cryptonote { //--------------------------------------------------------------- - void get_transaction_prefix_hash(const transaction_prefix& tx, crypto::hash& h); - crypto::hash get_transaction_prefix_hash(const transaction_prefix& tx); - bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash); - bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx); - bool construct_miner_tx(size_t height, size_t median_size, uint64_t already_generated_coins, size_t current_block_size, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce = blobdata(), size_t max_outs = 1); + void get_transaction_prefix_hash(const TransactionPrefix& tx, crypto::hash& h); + crypto::hash get_transaction_prefix_hash(const TransactionPrefix& tx); + bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, Transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash); + bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, Transaction& tx); struct tx_source_entry { @@ -47,14 +52,14 @@ namespace cryptonote struct tx_destination_entry { uint64_t amount; //money - account_public_address addr; //destination address + AccountPublicAddress addr; //destination address - tx_destination_entry() : amount(0), addr(AUTO_VAL_INIT(addr)) { } - tx_destination_entry(uint64_t a, const account_public_address &ad) : amount(a), addr(ad) { } + tx_destination_entry() : amount(0), addr(boost::value_initialized()) { } + tx_destination_entry(uint64_t a, const AccountPublicAddress &ad) : amount(a), addr(ad) { } }; //--------------------------------------------------------------- - bool construct_tx(const account_keys& sender_account_keys, const std::vector& sources, const std::vector& destinations, std::vector extra, transaction& tx, uint64_t unlock_time); + bool construct_tx(const account_keys& sender_account_keys, const std::vector& sources, const std::vector& destinations, std::vector extra, Transaction& tx, uint64_t unlock_time); template bool find_tx_extra_field_by_type(const std::vector& tx_extra_fields, T& field) @@ -69,44 +74,46 @@ namespace cryptonote bool parse_tx_extra(const std::vector& tx_extra, std::vector& tx_extra_fields); crypto::public_key get_tx_pub_key_from_extra(const std::vector& tx_extra); - crypto::public_key get_tx_pub_key_from_extra(const transaction& tx); - bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key); + crypto::public_key get_tx_pub_key_from_extra(const Transaction& tx); + bool add_tx_pub_key_to_extra(Transaction& tx, const crypto::public_key& tx_pub_key); bool add_extra_nonce_to_tx_extra(std::vector& tx_extra, const blobdata& extra_nonce); void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id); bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id); - bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, size_t output_index); - bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector& outs, uint64_t& money_transfered); - bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector& outs, uint64_t& money_transfered); - bool get_tx_fee(const transaction& tx, uint64_t & fee); - uint64_t get_tx_fee(const transaction& tx); - bool generate_key_image_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki); + bool append_mm_tag_to_extra(std::vector& tx_extra, const tx_extra_merge_mining_tag& mm_tag); + bool get_mm_tag_from_extra(const std::vector& tx_extra, tx_extra_merge_mining_tag& mm_tag); + bool is_out_to_acc(const account_keys& acc, const TransactionOutputToKey& out_key, const crypto::public_key& tx_pub_key, size_t keyIndex); + bool is_out_to_acc(const account_keys& acc, const TransactionOutputToKey& out_key, const crypto::key_derivation& derivation, size_t keyIndex); + bool lookup_acc_outs(const account_keys& acc, const Transaction& tx, const crypto::public_key& tx_pub_key, std::vector& outs, uint64_t& money_transfered); + bool lookup_acc_outs(const account_keys& acc, const Transaction& tx, std::vector& outs, uint64_t& money_transfered); + bool get_tx_fee(const Transaction& tx, uint64_t & fee); + uint64_t get_tx_fee(const Transaction& tx); + bool generate_key_image_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, KeyPair& in_ephemeral, crypto::key_image& ki); void get_blob_hash(const blobdata& blob, crypto::hash& res); crypto::hash get_blob_hash(const blobdata& blob); std::string short_hash_str(const crypto::hash& h); - crypto::hash get_transaction_hash(const transaction& t); - bool get_transaction_hash(const transaction& t, crypto::hash& res); - bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t& blob_size); - bool get_block_hashing_blob(const block& b, blobdata& blob); - bool get_block_hash(const block& b, crypto::hash& res); - crypto::hash get_block_hash(const block& b); - bool get_block_longhash(crypto::cn_context &context, const block& b, crypto::hash& res, uint64_t height); - crypto::hash get_block_longhash(crypto::cn_context &context, const block& b, uint64_t height); - bool generate_genesis_block(block& bl); - bool parse_and_validate_block_from_blob(const blobdata& b_blob, block& b); - bool get_inputs_money_amount(const transaction& tx, uint64_t& money); - uint64_t get_outs_money_amount(const transaction& tx); - bool check_inputs_types_supported(const transaction& tx); - bool check_outs_valid(const transaction& tx); - bool parse_amount(uint64_t& amount, const std::string& str_amount); + crypto::hash get_transaction_hash(const Transaction& t); + bool get_transaction_hash(const Transaction& t, crypto::hash& res); + bool get_transaction_hash(const Transaction& t, crypto::hash& res, size_t& blob_size); + bool get_block_hashing_blob(const Block& b, blobdata& blob); + bool get_parent_block_hashing_blob(const Block& b, blobdata& blob); + bool get_aux_block_header_hash(const Block& b, crypto::hash& res); + bool get_block_hash(const Block& b, crypto::hash& res); + crypto::hash get_block_hash(const Block& b); + bool get_block_longhash(crypto::cn_context &context, const Block& b, crypto::hash& res); + bool parse_and_validate_block_from_blob(const blobdata& b_blob, Block& b); + bool get_inputs_money_amount(const Transaction& tx, uint64_t& money); + uint64_t get_outs_money_amount(const Transaction& tx); + bool check_inputs_types_supported(const Transaction& tx); + bool check_outs_valid(const Transaction& tx); + bool checkMultisignatureInputsDiff(const Transaction& tx); - bool check_money_overflow(const transaction& tx); - bool check_outs_overflow(const transaction& tx); - bool check_inputs_overflow(const transaction& tx); - uint64_t get_block_height(const block& b); + bool check_money_overflow(const Transaction& tx); + bool check_outs_overflow(const Transaction& tx); + bool check_inputs_overflow(const Transaction& tx); + uint64_t get_block_height(const Block& b); std::vector relative_output_offsets_to_absolute(const std::vector& off); std::vector absolute_output_offsets_to_relative(const std::vector& off); - std::string print_money(uint64_t amount); //--------------------------------------------------------------- template bool t_serializable_object_to_blob(const t_object& to, blobdata& b_blob) @@ -134,10 +141,22 @@ namespace cryptonote } //--------------------------------------------------------------- template + bool get_object_blobsize(const t_object& o, size_t& size) { + blobdata blob; + if (!t_serializable_object_to_blob(o, blob)) { + size = (std::numeric_limits::max)(); + return false; + } + size = blob.size(); + return true; + } + //--------------------------------------------------------------- + template size_t get_object_blobsize(const t_object& o) { - blobdata b = t_serializable_object_to_blob(o); - return b.size(); + size_t size; + get_object_blobsize(o, size); + return size; } //--------------------------------------------------------------- template @@ -150,11 +169,10 @@ namespace cryptonote } //--------------------------------------------------------------- template - std::string obj_to_json_str(T& obj) - { + std::string obj_to_json_str(const T& obj) { std::stringstream ss; json_archive ar(ss, true); - bool r = ::serialization::serialize(ar, obj); + bool r = ::serialization::serialize(ar, *const_cast(&obj)); CHECK_AND_ASSERT_MES(r, "", "obj_to_json_str failed: serialization::serialize returned false"); return ss.str(); } @@ -201,13 +219,13 @@ namespace cryptonote } } //--------------------------------------------------------------- - blobdata block_to_blob(const block& b); - bool block_to_blob(const block& b, blobdata& b_blob); - blobdata tx_to_blob(const transaction& b); - bool tx_to_blob(const transaction& b, blobdata& b_blob); + blobdata block_to_blob(const Block& b); + bool block_to_blob(const Block& b, blobdata& b_blob); + blobdata tx_to_blob(const Transaction& b); + bool tx_to_blob(const Transaction& b, blobdata& b_blob); void get_tx_tree_hash(const std::vector& tx_hashes, crypto::hash& h); crypto::hash get_tx_tree_hash(const std::vector& tx_hashes); - crypto::hash get_tx_tree_hash(const block& b); + crypto::hash get_tx_tree_hash(const Block& b); #define CHECKED_GET_SPECIFIC_VARIANT(variant_var, specific_type, variable_name, fail_return_val) \ CHECK_AND_ASSERT_MES(variant_var.type() == typeid(specific_type), fail_return_val, "wrong variant type: " << variant_var.type().name() << ", expected " << typeid(specific_type).name()); \ diff --git a/src/cryptonote_core/difficulty.cpp b/src/cryptonote_core/difficulty.cpp index 7100da4e..eed5e004 100644 --- a/src/cryptonote_core/difficulty.cpp +++ b/src/cryptonote_core/difficulty.cpp @@ -75,50 +75,4 @@ namespace cryptonote { carry = cadc(high, top, carry); return !carry; } - - difficulty_type next_difficulty(vector timestamps, vector cumulative_difficulties, size_t target_seconds) { - //cutoff DIFFICULTY_LAG - if(timestamps.size() > DIFFICULTY_WINDOW) - { - timestamps.resize(DIFFICULTY_WINDOW); - cumulative_difficulties.resize(DIFFICULTY_WINDOW); - } - - - size_t length = timestamps.size(); - assert(length == cumulative_difficulties.size()); - if (length <= 1) { - return 1; - } - static_assert(DIFFICULTY_WINDOW >= 2, "Window is too small"); - assert(length <= DIFFICULTY_WINDOW); - sort(timestamps.begin(), timestamps.end()); - size_t cut_begin, cut_end; - static_assert(2 * DIFFICULTY_CUT <= DIFFICULTY_WINDOW - 2, "Cut length is too large"); - if (length <= DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT) { - cut_begin = 0; - cut_end = length; - } else { - cut_begin = (length - (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT) + 1) / 2; - cut_end = cut_begin + (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT); - } - assert(/*cut_begin >= 0 &&*/ cut_begin + 2 <= cut_end && cut_end <= length); - uint64_t time_span = timestamps[cut_end - 1] - timestamps[cut_begin]; - if (time_span == 0) { - time_span = 1; - } - difficulty_type total_work = cumulative_difficulties[cut_end - 1] - cumulative_difficulties[cut_begin]; - assert(total_work > 0); - uint64_t low, high; - mul(total_work, target_seconds, low, high); - if (high != 0 || low + time_span - 1 < low) { - return 0; - } - return (low + time_span - 1) / time_span; - } - - difficulty_type next_difficulty(vector timestamps, vector cumulative_difficulties) - { - return next_difficulty(std::move(timestamps), std::move(cumulative_difficulties), DIFFICULTY_TARGET); - } } diff --git a/src/cryptonote_core/difficulty.h b/src/cryptonote_core/difficulty.h index 5a4c803c..f10937c0 100644 --- a/src/cryptonote_core/difficulty.h +++ b/src/cryptonote_core/difficulty.h @@ -27,6 +27,4 @@ namespace cryptonote typedef std::uint64_t difficulty_type; bool check_hash(const crypto::hash &hash, difficulty_type difficulty); - difficulty_type next_difficulty(std::vector timestamps, std::vector cumulative_difficulties); - difficulty_type next_difficulty(std::vector timestamps, std::vector cumulative_difficulties, size_t target_seconds); } diff --git a/src/cryptonote_core/i_miner_handler.h b/src/cryptonote_core/i_miner_handler.h new file mode 100644 index 00000000..ff3d20fb --- /dev/null +++ b/src/cryptonote_core/i_miner_handler.h @@ -0,0 +1,31 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include "cryptonote_core/cryptonote_basic.h" +#include "cryptonote_core/difficulty.h" + +namespace cryptonote { + struct i_miner_handler { + virtual bool handle_block_found(Block& b) = 0; + virtual bool get_block_template(Block& b, const AccountPublicAddress& adr, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce) = 0; + + protected: + ~i_miner_handler(){}; + }; +} diff --git a/src/cryptonote_core/miner.cpp b/src/cryptonote_core/miner.cpp index 3fad129a..fc64a01a 100644 --- a/src/cryptonote_core/miner.cpp +++ b/src/cryptonote_core/miner.cpp @@ -17,10 +17,13 @@ #include #include -#include + +#include +#include #include #include -#include +#include + #include "misc_language.h" #include "include_base_utils.h" #include "cryptonote_basic_impl.h" @@ -35,8 +38,8 @@ using namespace epee; #include "miner.h" - - +#include +#include namespace cryptonote { @@ -49,13 +52,14 @@ namespace cryptonote } - miner::miner(i_miner_handler* phandler):m_stop(1), - m_template(boost::value_initialized()), + miner::miner(const Currency& currency, i_miner_handler* phandler): + m_currency(currency), + 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_threads_total(0), m_starter_nonce(0), @@ -65,38 +69,47 @@ namespace cryptonote m_do_mining(false), m_current_hash_rate(0) { - } //----------------------------------------------------------------------------------------------------- - miner::~miner() - { + miner::~miner() { stop(); } //----------------------------------------------------------------------------------------------------- - bool miner::set_block_template(const block& bl, const difficulty_type& di, uint64_t height) - { + bool miner::set_block_template(const Block& bl, const difficulty_type& di) { CRITICAL_REGION_LOCAL(m_template_lock); m_template = bl; + + if (BLOCK_MAJOR_VERSION_2 == m_template.majorVersion) { + cryptonote::tx_extra_merge_mining_tag mm_tag; + mm_tag.depth = 0; + if (!cryptonote::get_aux_block_header_hash(m_template, mm_tag.merkle_root)) { + return false; + } + + m_template.parentBlock.minerTx.extra.clear(); + if (!cryptonote::append_mm_tag_to_extra(m_template.parentBlock.minerTx.extra, mm_tag)) { + return false; + } + } + m_diffic = di; - m_height = height; ++m_template_no; m_starter_nonce = crypto::rand(); return true; } //----------------------------------------------------------------------------------------------------- - bool miner::on_block_chain_update() - { - if(!is_mining()) + bool miner::on_block_chain_update() { + if (!is_mining()) { return true; + } return request_block_template(); } //----------------------------------------------------------------------------------------------------- - bool miner::request_block_template() - { - block bl = AUTO_VAL_INIT(bl); + bool miner::request_block_template() { + Block bl = AUTO_VAL_INIT(bl); difficulty_type di = AUTO_VAL_INIT(di); - uint64_t height = AUTO_VAL_INIT(height); + uint64_t height; cryptonote::blobdata extra_nonce; if(m_extra_messages.size() && m_config.current_extra_message_index < m_extra_messages.size()) { @@ -108,7 +121,7 @@ namespace cryptonote LOG_ERROR("Failed to get_block_template(), stopping mining"); return false; } - set_block_template(bl, di, height); + set_block_template(bl, di); return true; } //----------------------------------------------------------------------------------------------------- @@ -180,14 +193,13 @@ namespace cryptonote } m_config_folder_path = boost::filesystem::path(command_line::get_arg(vm, arg_extra_messages)).parent_path().string(); m_config = AUTO_VAL_INIT(m_config); - epee::serialization::load_t_from_json_file(m_config, m_config_folder_path + "/" + MINER_CONFIG_FILE_NAME); + epee::serialization::load_t_from_json_file(m_config, m_config_folder_path + "/" + cryptonote::parameters::MINER_CONFIG_FILE_NAME); LOG_PRINT_L0("Loaded " << m_extra_messages.size() << " extra messages, current index " << m_config.current_extra_message_index); } if(command_line::has_arg(vm, arg_start_mining)) { - if(!cryptonote::get_account_address_from_str(m_mine_address, command_line::get_arg(vm, arg_start_mining))) - { + if (!m_currency.parseAccountAddressString(command_line::get_arg(vm, arg_start_mining), m_mine_address)) { LOG_ERROR("Target account address " << command_line::get_arg(vm, arg_start_mining) << " has wrong format, starting daemon canceled"); return false; } @@ -207,7 +219,7 @@ namespace cryptonote return !m_stop; } //----------------------------------------------------------------------------------------------------- - bool miner::start(const account_public_address& adr, size_t threads_count, const boost::thread::attributes& attrs) + bool miner::start(const AccountPublicAddress& adr, size_t threads_count, const boost::thread::attributes& attrs) { m_mine_address = adr; m_threads_total = static_cast(threads_count); @@ -266,18 +278,61 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------------- - bool miner::find_nonce_for_given_block(crypto::cn_context &context, block& bl, const difficulty_type& diffic, uint64_t height) - { - for(; bl.nonce != std::numeric_limits::max(); bl.nonce++) - { - crypto::hash h; - get_block_longhash(context, bl, h, height); + bool miner::find_nonce_for_given_block(crypto::cn_context &context, Block& bl, const difficulty_type& diffic) { - if(check_hash(h, diffic)) - { - return true; + unsigned nthreads = std::thread::hardware_concurrency(); + + if (nthreads > 0 && diffic > 5) { + std::vector> threads(nthreads); + std::atomic foundNonce; + std::atomic found(false); + uint32_t startNonce = crypto::rand(); + + for (unsigned i = 0; i < nthreads; ++i) { + threads[i] = std::async(std::launch::async, [&, i]() { + crypto::cn_context localctx; + crypto::hash h; + + Block lb(bl); // copy to local block + + for (uint32_t nonce = startNonce + i; !found; nonce += nthreads) { + lb.nonce = nonce; + + if (!get_block_longhash(localctx, lb, h)) { + return; + } + + if (check_hash(h, diffic)) { + foundNonce = nonce; + found = true; + return; + } + } + }); + } + + for (auto& t : threads) { + t.wait(); + } + + if (found) { + bl.nonce = foundNonce.load(); + } + + return found; + } else { + for (; bl.nonce != std::numeric_limits::max(); bl.nonce++) { + crypto::hash h; + if (!get_block_longhash(context, bl, h)) { + return false; + } + + if (check_hash(h, diffic)) { + return true; + } } } + return false; } //----------------------------------------------------------------------------------------------------- @@ -319,11 +374,10 @@ namespace cryptonote LOG_PRINT_L0("Miner thread was started ["<< th_local_index << "]"); log_space::log_singletone::set_thread_log_prefix(std::string("[miner ") + std::to_string(th_local_index) + "]"); uint32_t nonce = m_starter_nonce + th_local_index; - uint64_t height = 0; difficulty_type local_diff = 0; uint32_t local_template_ver = 0; crypto::cn_context context; - block b; + Block b; while(!m_stop) { if(m_pausers_count)//anti split workaround @@ -338,7 +392,6 @@ namespace cryptonote CRITICAL_REGION_BEGIN(m_template_lock); b = m_template; local_diff = m_diffic; - height = m_height; CRITICAL_REGION_END(); local_template_ver = m_template_no; nonce = m_starter_nonce + th_local_index; @@ -353,9 +406,12 @@ namespace cryptonote b.nonce = nonce; crypto::hash h; - get_block_longhash(context, b, h, height); + if (!m_stop && !get_block_longhash(context, b, h)) { + LOG_ERROR("Failed to get block long hash"); + m_stop = true; + } - if(!m_stop && check_hash(h, local_diff)) + if (!m_stop && check_hash(h, local_diff)) { //we lucky! ++m_config.current_extra_message_index; @@ -366,7 +422,7 @@ namespace cryptonote }else { //success update, lets update config - epee::serialization::store_t_to_json_file(m_config, m_config_folder_path + "/" + MINER_CONFIG_FILE_NAME); + epee::serialization::store_t_to_json_file(m_config, m_config_folder_path + "/" + cryptonote::parameters::MINER_CONFIG_FILE_NAME); } } diff --git a/src/cryptonote_core/miner.h b/src/cryptonote_core/miner.h index 1b0c19ab..1f492761 100644 --- a/src/cryptonote_core/miner.h +++ b/src/cryptonote_core/miner.h @@ -17,37 +17,29 @@ #pragma once -#include #include -#include "cryptonote_basic.h" -#include "difficulty.h" + +#include + +// epee +#include "serialization/keyvalue_serialization.h" #include "math_helper.h" +#include "cryptonote_core/cryptonote_basic.h" +#include "cryptonote_core/Currency.h" +#include "cryptonote_core/difficulty.h" +#include "cryptonote_core/i_miner_handler.h" -namespace cryptonote -{ - - struct i_miner_handler - { - virtual bool handle_block_found(block& b) = 0; - virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce) = 0; - protected: - ~i_miner_handler(){}; - }; - - /************************************************************************/ - /* */ - /************************************************************************/ - class miner - { +namespace cryptonote { + class miner { public: - miner(i_miner_handler* phandler); + miner(const Currency& currency, i_miner_handler* phandler); ~miner(); bool init(const boost::program_options::variables_map& vm); static void init_options(boost::program_options::options_description& desc); - bool set_block_template(const block& bl, const difficulty_type& diffic, uint64_t height); + bool set_block_template(const Block& bl, const difficulty_type& diffic); bool on_block_chain_update(); - bool start(const account_public_address& adr, size_t threads_count, const boost::thread::attributes& attrs); + bool start(const AccountPublicAddress& adr, size_t threads_count, const boost::thread::attributes& attrs); uint64_t get_speed(); void send_stop_signal(); bool stop(); @@ -55,7 +47,7 @@ namespace cryptonote bool on_idle(); void on_synchronized(); //synchronous analog (for fast calls) - static bool find_nonce_for_given_block(crypto::cn_context &context, block& bl, const difficulty_type& diffic, uint64_t height); + static bool find_nonce_for_given_block(crypto::cn_context &context, Block& bl, const difficulty_type& diffic); void pause(); void resume(); void do_print_hashrate(bool do_hr); @@ -75,13 +67,13 @@ namespace cryptonote }; + const Currency& m_currency; volatile uint32_t m_stop; epee::critical_section m_template_lock; - block m_template; + Block m_template; std::atomic m_template_no; std::atomic m_starter_nonce; difficulty_type m_diffic; - uint64_t m_height; volatile uint32_t m_thread_index; volatile uint32_t m_threads_total; std::atomic m_pausers_count; @@ -90,7 +82,7 @@ namespace cryptonote std::list m_threads; epee::critical_section m_threads_lock; i_miner_handler* m_phandler; - account_public_address m_mine_address; + AccountPublicAddress m_mine_address; epee::math_helper::once_a_time_seconds<5> m_update_block_template_interval; epee::math_helper::once_a_time_seconds<2> m_update_merge_hr_interval; std::vector m_extra_messages; @@ -103,6 +95,5 @@ namespace cryptonote std::list m_last_hash_rates; bool m_do_print_hashrate; bool m_do_mining; - }; } diff --git a/src/cryptonote_core/tx_extra.h b/src/cryptonote_core/tx_extra.h index 1c73e6ec..325bc2a8 100644 --- a/src/cryptonote_core/tx_extra.h +++ b/src/cryptonote_core/tx_extra.h @@ -17,6 +17,12 @@ #pragma once +#include "crypto/crypto.h" +#include "crypto/hash.h" +#include "serialization/binary_archive.h" +#include "serialization/crypto.h" +#include "serialization/serialization.h" +#include "serialization/variant.h" #define TX_EXTRA_PADDING_MAX_COUNT 255 #define TX_EXTRA_NONCE_MAX_COUNT 255 diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index ff80b60d..27beaa38 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -15,32 +15,100 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . -#include -#include -#include -#include - #include "tx_pool.h" -#include "cryptonote_format_utils.h" -#include "cryptonote_boost_serialization.h" -#include "cryptonote_config.h" -#include "blockchain_storage.h" + +#include +#include +#include +#include + +#include + +// epee +#include "misc_language.h" +#include "misc_log_ex.h" +#include "warnings.h" + #include "common/boost_serialization_helper.h" #include "common/int-util.h" -#include "misc_language.h" -#include "warnings.h" +#include "common/util.h" #include "crypto/hash.h" +#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_core/cryptonote_boost_serialization.h" +#include "cryptonote_config.h" + DISABLE_VS_WARNINGS(4244 4345 4503) //'boost::foreach_detail_::or_' : decorated name length exceeded, name was truncated -#define TRANSACTION_SIZE_LIMIT (((CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE * 125) / 100) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE) - namespace cryptonote { + //--------------------------------------------------------------------------------- - tx_memory_pool::tx_memory_pool(blockchain_storage& bchs): m_blockchain(bchs) { + // BlockTemplate + //--------------------------------------------------------------------------------- + class BlockTemplate { + public: + + bool addTransaction(const crypto::hash& txid, const Transaction& tx) { + if (!canAdd(tx)) + return false; + + for (const auto& in : tx.vin) { + if (in.type() == typeid(TransactionInputToKey)) { + auto r = m_keyImages.insert(boost::get(in).keyImage); + (void)r; //just to make compiler to shut up + assert(r.second); + } else if (in.type() == typeid(TransactionInputMultisignature)) { + const auto& msig = boost::get(in); + auto r = m_usedOutputs.insert(std::make_pair(msig.amount, msig.outputIndex)); + (void)r; //just to make compiler to shut up + assert(r.second); + } + } + + m_txHashes.push_back(txid); + return true; + } + + const std::vector& getTransactions() const { + return m_txHashes; + } + + private: + + bool canAdd(const Transaction& tx) { + for (const auto& in : tx.vin) { + if (in.type() == typeid(TransactionInputToKey)) { + if (m_keyImages.count(boost::get(in).keyImage)) { + return false; + } + } else if (in.type() == typeid(TransactionInputMultisignature)) { + const auto& msig = boost::get(in); + if (m_usedOutputs.count(std::make_pair(msig.amount, msig.outputIndex))) { + return false; + } + } + } + return true; + } + + std::unordered_set m_keyImages; + std::set> m_usedOutputs; + std::vector m_txHashes; + }; + + using CryptoNote::BlockInfo; + + //--------------------------------------------------------------------------------- + tx_memory_pool::tx_memory_pool(const cryptonote::Currency& currency, CryptoNote::ITransactionValidator& validator, CryptoNote::ITimeProvider& timeProvider) : + m_currency(currency), + m_validator(validator), + m_timeProvider(timeProvider), + m_txCheckInterval(60, timeProvider), + m_fee_index(boost::get<1>(m_transactions)) { } + //--------------------------------------------------------------------------------- - bool tx_memory_pool::add_tx(const transaction &tx, /*const crypto::hash& tx_prefix_hash,*/ const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool kept_by_block) { + bool tx_memory_pool::add_tx(const Transaction &tx, /*const crypto::hash& tx_prefix_hash,*/ const crypto::hash &id, size_t blobSize, tx_verification_context& tvc, bool keptByBlock) { if (!check_inputs_types_supported(tx)) { tvc.m_verifivation_failed = true; return false; @@ -55,134 +123,103 @@ namespace cryptonote { uint64_t outputs_amount = get_outs_money_amount(tx); if (outputs_amount >= inputs_amount) { - LOG_PRINT_L0("transaction use more money then it has: use " << print_money(outputs_amount) << ", have " << print_money(inputs_amount)); + LOG_PRINT_L0("transaction use more money then it has: use " << m_currency.formatAmount(outputs_amount) << + ", have " << m_currency.formatAmount(inputs_amount)); tvc.m_verifivation_failed = true; return false; } - uint64_t fee = inputs_amount - outputs_amount; - if (!kept_by_block && fee < MINIMUM_FEE) { - LOG_ERROR("transaction fee is not enought: " << print_money(fee) << ", minumim fee: " << print_money(MINIMUM_FEE)); - tvc.m_verifivation_failed = true; - return false; - } - - if (!kept_by_block && blob_size >= TRANSACTION_SIZE_LIMIT) { - LOG_ERROR("transaction is too big: " << blob_size << " bytes, maximum size: " << TRANSACTION_SIZE_LIMIT); + const uint64_t fee = inputs_amount - outputs_amount; + if (!keptByBlock && fee < m_currency.minimumFee()) { + LOG_PRINT_L0("transaction fee is not enought: " << m_currency.formatAmount(fee) << + ", minumim fee: " << m_currency.formatAmount(m_currency.minimumFee())); tvc.m_verifivation_failed = true; + tvc.m_tx_fee_too_small = true; return false; } //check key images for transaction if it is not kept by block - if (!kept_by_block) { - if (have_tx_keyimges_as_spent(tx)) { - LOG_ERROR("Transaction with id= "<< id << " used already spent key images"); + if (!keptByBlock) { + CRITICAL_REGION_LOCAL(m_transactions_lock); + if (haveSpentInputs(tx)) { + LOG_PRINT_L0("Transaction with id= " << id << " used already spent inputs"); tvc.m_verifivation_failed = true; return false; } } - crypto::hash max_used_block_id = null_hash; - uint64_t max_used_block_height = 0; - bool ch_inp_res = m_blockchain.check_tx_inputs(tx, max_used_block_height, max_used_block_id); - CRITICAL_REGION_LOCAL(m_transactions_lock); - if (!ch_inp_res) { - if (kept_by_block) { - //anyway add this transaction to pool, because it related to block - auto txd_p = m_transactions.insert(transactions_container::value_type(id, tx_details())); - CHECK_AND_ASSERT_MES(txd_p.second, false, "transaction already exists at inserting in memory pool"); - txd_p.first->second.blob_size = blob_size; - txd_p.first->second.tx = tx; - txd_p.first->second.fee = inputs_amount - outputs_amount; - txd_p.first->second.max_used_block_id = null_hash; - txd_p.first->second.max_used_block_height = 0; - txd_p.first->second.kept_by_block = kept_by_block; - tvc.m_verifivation_impossible = true; - tvc.m_added_to_pool = true; - } else { + BlockInfo maxUsedBlock; + + // check inputs + bool inputsValid = m_validator.checkTransactionInputs(tx, maxUsedBlock); + + if (!inputsValid) { + if (!keptByBlock) { LOG_PRINT_L0("tx used wrong inputs, rejected"); tvc.m_verifivation_failed = true; return false; } - } else { - //update transactions container - auto txd_p = m_transactions.insert(transactions_container::value_type(id, tx_details())); - CHECK_AND_ASSERT_MES(txd_p.second, false, "intrnal error: transaction already exists at inserting in memorypool"); - txd_p.first->second.blob_size = blob_size; - txd_p.first->second.tx = tx; - txd_p.first->second.kept_by_block = kept_by_block; - txd_p.first->second.fee = inputs_amount - outputs_amount; - txd_p.first->second.max_used_block_id = max_used_block_id; - txd_p.first->second.max_used_block_height = max_used_block_height; - txd_p.first->second.last_failed_height = 0; - txd_p.first->second.last_failed_id = null_hash; - tvc.m_added_to_pool = true; - if (txd_p.first->second.fee > 0) { - tvc.m_should_be_relayed = true; - } + maxUsedBlock.clear(); + tvc.m_verifivation_impossible = true; } + CRITICAL_REGION_LOCAL(m_transactions_lock); + + // add to pool + { + TransactionDetails txd; + + txd.id = id; + txd.blobSize = blobSize; + txd.tx = tx; + txd.fee = fee; + txd.keptByBlock = keptByBlock; + txd.receiveTime = m_timeProvider.now(); + + txd.maxUsedBlock = maxUsedBlock; + txd.lastFailedBlock.clear(); + + auto txd_p = m_transactions.insert(std::move(txd)); + CHECK_AND_ASSERT_MES(txd_p.second, false, "transaction already exists at inserting in memory pool"); + } + + tvc.m_added_to_pool = true; + + if (inputsValid && fee > 0) + tvc.m_should_be_relayed = true; + tvc.m_verifivation_failed = true; - //update image_keys container, here should everything goes ok. - for (const auto& in : tx.vin) { - CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, txin, false); - std::unordered_set& kei_image_set = m_spent_key_images[txin.k_image]; - CHECK_AND_ASSERT_MES(kept_by_block || kei_image_set.size() == 0, false, "internal error: keeped_by_block=" << kept_by_block - << ", kei_image_set.size()=" << kei_image_set.size() << ENDL << "txin.k_image=" << txin.k_image << ENDL - << "tx_id=" << id ); - auto ins_res = kei_image_set.insert(id); - CHECK_AND_ASSERT_MES(ins_res.second, false, "internal error: try to insert duplicate iterator in key_image set"); - } + + if (!addTransactionInputs(id, tx, keptByBlock)) + return false; tvc.m_verifivation_failed = false; //succeed return true; } //--------------------------------------------------------------------------------- - bool tx_memory_pool::add_tx(const transaction &tx, tx_verification_context& tvc, bool keeped_by_block) { + bool tx_memory_pool::add_tx(const Transaction &tx, tx_verification_context& tvc, bool keeped_by_block) { crypto::hash h = null_hash; - size_t blob_size = 0; - get_transaction_hash(tx, h, blob_size); - return add_tx(tx, h, blob_size, tvc, keeped_by_block); + size_t blobSize = 0; + get_transaction_hash(tx, h, blobSize); + return add_tx(tx, h, blobSize, tvc, keeped_by_block); } //--------------------------------------------------------------------------------- - bool tx_memory_pool::remove_transaction_keyimages(const transaction& tx) { - CRITICAL_REGION_LOCAL(m_transactions_lock); - crypto::hash tx_id = get_transaction_hash(tx); - for (const txin_v& vi : tx.vin) { - CHECKED_GET_SPECIFIC_VARIANT(vi, const txin_to_key, txin, false); - auto it = m_spent_key_images.find(txin.k_image); - CHECK_AND_ASSERT_MES(it != m_spent_key_images.end(), false, "failed to find transaction input in key images. img=" << txin.k_image << std::endl - << "transaction id = " << tx_id); - std::unordered_set& key_image_set = it->second; - CHECK_AND_ASSERT_MES(!key_image_set.empty(), false, "empty key_image set, img=" << txin.k_image << std::endl - << "transaction id = " << tx_id); - - auto it_in_set = key_image_set.find(tx_id); - CHECK_AND_ASSERT_MES(it_in_set != key_image_set.end(), false, "transaction id not found in key_image set, img=" << txin.k_image << std::endl - << "transaction id = " << tx_id); - key_image_set.erase(it_in_set); - if (key_image_set.empty()) { - //it is now empty hash container for this key_image - m_spent_key_images.erase(it); - } - } - return true; - } - //--------------------------------------------------------------------------------- - bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee) { + bool tx_memory_pool::take_tx(const crypto::hash &id, Transaction &tx, size_t& blobSize, uint64_t& fee) { CRITICAL_REGION_LOCAL(m_transactions_lock); auto it = m_transactions.find(id); if (it == m_transactions.end()) { return false; } - tx = it->second.tx; - blob_size = it->second.blob_size; - fee = it->second.fee; - remove_transaction_keyimages(it->second.tx); - m_transactions.erase(it); + auto& txd = *it; + + tx = txd.tx; + blobSize = txd.blobSize; + fee = txd.fee; + + removeTransaction(it); return true; } //--------------------------------------------------------------------------------- @@ -191,10 +228,10 @@ namespace cryptonote { return m_transactions.size(); } //--------------------------------------------------------------------------------- - void tx_memory_pool::get_transactions(std::list& txs) const { + void tx_memory_pool::get_transactions(std::list& txs) const { CRITICAL_REGION_LOCAL(m_transactions_lock); for (const auto& tx_vt : m_transactions) { - txs.push_back(tx_vt.second.tx); + txs.push_back(tx_vt.tx); } } //--------------------------------------------------------------------------------- @@ -214,22 +251,6 @@ namespace cryptonote { return false; } //--------------------------------------------------------------------------------- - bool tx_memory_pool::have_tx_keyimges_as_spent(const transaction& tx) const { - CRITICAL_REGION_LOCAL(m_transactions_lock); - for (const auto& in : tx.vin) { - CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, true);//should never fail - if (have_tx_keyimg_as_spent(tokey_in.k_image)) { - return true; - } - } - return false; - } - //--------------------------------------------------------------------------------- - bool tx_memory_pool::have_tx_keyimg_as_spent(const crypto::key_image& key_im) const { - CRITICAL_REGION_LOCAL(m_transactions_lock); - return m_spent_key_images.end() != m_spent_key_images.find(key_im); - } - //--------------------------------------------------------------------------------- void tx_memory_pool::lock() const { m_transactions_lock.lock(); } @@ -238,110 +259,74 @@ namespace cryptonote { m_transactions_lock.unlock(); } //--------------------------------------------------------------------------------- - bool tx_memory_pool::is_transaction_ready_to_go(tx_details& txd) const { - //not the best implementation at this time, sorry :( - //check is ring_signature already checked ? - if (txd.max_used_block_id == null_hash) { - //not checked, lets try to check - if (txd.last_failed_id != null_hash && m_blockchain.get_current_blockchain_height() > txd.last_failed_height && txd.last_failed_id == m_blockchain.get_block_id_by_height(txd.last_failed_height)) { - return false;//we already sure that this tx is broken for this height - } + bool tx_memory_pool::is_transaction_ready_to_go(const Transaction& tx, TransactionCheckInfo& txd) const { - if (!m_blockchain.check_tx_inputs(txd.tx, txd.max_used_block_height, txd.max_used_block_id)) { - txd.last_failed_height = m_blockchain.get_current_blockchain_height()-1; - txd.last_failed_id = m_blockchain.get_block_id_by_height(txd.last_failed_height); - return false; - } - } else { - if (txd.max_used_block_height >= m_blockchain.get_current_blockchain_height()) { - return false; - } - if (m_blockchain.get_block_id_by_height(txd.max_used_block_height) != txd.max_used_block_id) { - //if we already failed on this height and id, skip actual ring signature check - if (txd.last_failed_id == m_blockchain.get_block_id_by_height(txd.last_failed_height)) { - return false; - } - //check ring signature again, it is possible (with very small chance) that this transaction become again valid - if (!m_blockchain.check_tx_inputs(txd.tx, txd.max_used_block_height, txd.max_used_block_id)) { - txd.last_failed_height = m_blockchain.get_current_blockchain_height()-1; - txd.last_failed_id = m_blockchain.get_block_id_by_height(txd.last_failed_height); - return false; - } - } - } + if (!m_validator.checkTransactionInputs(tx, txd.maxUsedBlock, txd.lastFailedBlock)) + return false; //if we here, transaction seems valid, but, anyway, check for key_images collisions with blockchain, just to be sure - if (m_blockchain.have_tx_keyimges_as_spent(txd.tx)) { + if (m_validator.haveSpentKeyImages(tx)) return false; - } //transaction is ok. return true; } //--------------------------------------------------------------------------------- - bool tx_memory_pool::have_key_images(const std::unordered_set& k_images, const transaction& tx) { - for (size_t i = 0; i!= tx.vin.size(); i++) { - CHECKED_GET_SPECIFIC_VARIANT(tx.vin[i], const txin_to_key, itk, false); - if (k_images.count(itk.k_image)) { - return true; - } - } - return false; - } - //--------------------------------------------------------------------------------- - bool tx_memory_pool::append_key_images(std::unordered_set& k_images, const transaction& tx) { - for (size_t i = 0; i!= tx.vin.size(); i++) { - CHECKED_GET_SPECIFIC_VARIANT(tx.vin[i], const txin_to_key, itk, false); - auto i_res = k_images.insert(itk.k_image); - CHECK_AND_ASSERT_MES(i_res.second, false, "internal error: key images pool cache - inserted duplicate image in set: " << itk.k_image); - } - return true; - } - //--------------------------------------------------------------------------------- std::string tx_memory_pool::print_pool(bool short_format) const { std::stringstream ss; CRITICAL_REGION_LOCAL(m_transactions_lock); - for (const transactions_container::value_type& txe : m_transactions) { - const tx_details& txd = txe.second; - ss << "id: " << txe.first << std::endl; + for (const auto& txd : m_fee_index) { + ss << "id: " << txd.id << std::endl; if (!short_format) { - ss << obj_to_json_str(*const_cast(&txd.tx)) << std::endl; + ss << obj_to_json_str(txd.tx) << std::endl; } - ss << "blob_size: " << txd.blob_size << std::endl - << "fee: " << print_money(txd.fee) << std::endl - << "kept_by_block: " << (txd.kept_by_block ? 'T' : 'F') << std::endl - << "max_used_block_height: " << txd.max_used_block_height << std::endl - << "max_used_block_id: " << txd.max_used_block_id << std::endl - << "last_failed_height: " << txd.last_failed_height << std::endl - << "last_failed_id: " << txd.last_failed_id << std::endl; + ss << "blobSize: " << txd.blobSize << std::endl + << "fee: " << m_currency.formatAmount(txd.fee) << std::endl + << "keptByBlock: " << (txd.keptByBlock ? 'T' : 'F') << std::endl + << "max_used_block_height: " << txd.maxUsedBlock.height << std::endl + << "max_used_block_id: " << txd.maxUsedBlock.id << std::endl + << "last_failed_height: " << txd.lastFailedBlock.height << std::endl + << "last_failed_id: " << txd.lastFailedBlock.id << std::endl + << "recieved: " << std::ctime(&txd.receiveTime) << std::endl; } return ss.str(); } //--------------------------------------------------------------------------------- - bool tx_memory_pool::fill_block_template(block& bl, size_t median_size, uint64_t already_generated_coins, size_t& total_size, uint64_t& fee) { + bool tx_memory_pool::fill_block_template(Block& bl, size_t median_size, size_t maxCumulativeSize, + uint64_t already_generated_coins, size_t& total_size, uint64_t& fee) { CRITICAL_REGION_LOCAL(m_transactions_lock); total_size = 0; fee = 0; - size_t max_total_size = (125 * median_size) / 100 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; - std::unordered_set k_images; - for (transactions_container::value_type& tx : m_transactions) { - if (max_total_size < total_size + tx.second.blob_size) { + size_t max_total_size = (125 * median_size) / 100 - m_currency.minerTxBlobReservedSize(); + max_total_size = std::min(max_total_size, maxCumulativeSize); + + BlockTemplate blockTemplate; + + for (auto i = m_fee_index.begin(); i != m_fee_index.end(); ++i) { + const auto& txd = *i; + + if (max_total_size < total_size + txd.blobSize) { continue; } - if (!is_transaction_ready_to_go(tx.second) || have_key_images(k_images, tx.second.tx)) { - continue; - } + TransactionCheckInfo checkInfo(txd); + bool ready = is_transaction_ready_to_go(txd.tx, checkInfo); - bl.tx_hashes.push_back(tx.first); - total_size += tx.second.blob_size; - fee += tx.second.fee; - append_key_images(k_images, tx.second.tx); + // update item state + m_fee_index.modify(i, [&checkInfo](TransactionCheckInfo& item) { + item = checkInfo; + }); + + if (ready && blockTemplate.addTransaction(txd.id, txd.tx)) { + total_size += txd.blobSize; + fee += txd.fee; + } } + bl.txHashes = blockTemplate.getTransactions(); return true; } //--------------------------------------------------------------------------------- @@ -349,7 +334,7 @@ namespace cryptonote { CRITICAL_REGION_LOCAL(m_transactions_lock); m_config_folder = config_folder; - std::string state_file_path = config_folder + "/" + CRYPTONOTE_POOLDATA_FILENAME; + std::string state_file_path = config_folder + "/" + m_currency.txPoolFileName(); boost::system::error_code ec; if (!boost::filesystem::exists(state_file_path, ec)) { return true; @@ -360,17 +345,8 @@ namespace cryptonote { m_transactions.clear(); m_spent_key_images.clear(); + m_spentOutputs.clear(); } - - for (auto it = m_transactions.begin(); it != m_transactions.end(); ) { - auto it2 = it++; - if (it2->second.blob_size >= TRANSACTION_SIZE_LIMIT) { - LOG_PRINT_L0("Transaction " << get_transaction_hash(it2->second.tx) << " is too big (" << it2->second.blob_size << " bytes), removing it from pool"); - remove_transaction_keyimages(it2->second.tx); - m_transactions.erase(it2); - } - } - // Ignore deserialization error return true; } @@ -381,11 +357,116 @@ namespace cryptonote { return false; } - std::string state_file_path = m_config_folder + "/" + CRYPTONOTE_POOLDATA_FILENAME; + std::string state_file_path = m_config_folder + "/" + m_currency.txPoolFileName(); bool res = tools::serialize_obj_to_file(*this, state_file_path); if (!res) { LOG_PRINT_L0("Failed to serialize memory pool to file " << state_file_path); } return true; } + + //--------------------------------------------------------------------------------- + void tx_memory_pool::on_idle() { + m_txCheckInterval.call([this](){ return removeExpiredTransactions(); }); + } + + //--------------------------------------------------------------------------------- + bool tx_memory_pool::removeExpiredTransactions() { + CRITICAL_REGION_LOCAL(m_transactions_lock); + + auto now = m_timeProvider.now(); + + for (auto it = m_transactions.begin(); it != m_transactions.end();) { + uint64_t txAge = now - it->receiveTime; + bool remove = txAge > (it->keptByBlock ? m_currency.mempoolTxFromAltBlockLiveTime() : m_currency.mempoolTxLiveTime()); + + if (remove) { + LOG_PRINT_L2("Tx " << it->id << " removed from tx pool due to outdated, age: " << txAge); + it = removeTransaction(it); + } else { + ++it; + } + } + return true; + } + + tx_memory_pool::tx_container_t::iterator tx_memory_pool::removeTransaction(tx_memory_pool::tx_container_t::iterator i) { + removeTransactionInputs(i->id, i->tx, i->keptByBlock); + return m_transactions.erase(i); + } + + bool tx_memory_pool::removeTransactionInputs(const crypto::hash& tx_id, const Transaction& tx, bool keptByBlock) { + for (const auto& in : tx.vin) { + if (in.type() == typeid(TransactionInputToKey)) { + const auto& txin = boost::get(in); + auto it = m_spent_key_images.find(txin.keyImage); + CHECK_AND_ASSERT_MES(it != m_spent_key_images.end(), false, "failed to find transaction input in key images. img=" << txin.keyImage << std::endl + << "transaction id = " << tx_id); + std::unordered_set& key_image_set = it->second; + CHECK_AND_ASSERT_MES(!key_image_set.empty(), false, "empty key_image set, img=" << txin.keyImage << std::endl + << "transaction id = " << tx_id); + + auto it_in_set = key_image_set.find(tx_id); + CHECK_AND_ASSERT_MES(it_in_set != key_image_set.end(), false, "transaction id not found in key_image set, img=" << txin.keyImage << std::endl + << "transaction id = " << tx_id); + key_image_set.erase(it_in_set); + if (key_image_set.empty()) { + //it is now empty hash container for this key_image + m_spent_key_images.erase(it); + } + } else if (in.type() == typeid(TransactionInputMultisignature)) { + if (!keptByBlock) { + const auto& msig = boost::get(in); + auto output = GlobalOutput(msig.amount, msig.outputIndex); + assert(m_spentOutputs.count(output)); + m_spentOutputs.erase(output); + } + } + } + + return true; + } + + //--------------------------------------------------------------------------------- + bool tx_memory_pool::addTransactionInputs(const crypto::hash& id, const Transaction& tx, bool keptByBlock) { + // should not fail + for (const auto& in : tx.vin) { + if (in.type() == typeid(TransactionInputToKey)) { + const auto& txin = boost::get(in); + std::unordered_set& kei_image_set = m_spent_key_images[txin.keyImage]; + CHECK_AND_ASSERT_MES(keptByBlock || kei_image_set.size() == 0, false, "internal error: keptByBlock=" << keptByBlock + << ", kei_image_set.size()=" << kei_image_set.size() << ENDL << "txin.keyImage=" << txin.keyImage << ENDL + << "tx_id=" << id); + auto ins_res = kei_image_set.insert(id); + CHECK_AND_ASSERT_MES(ins_res.second, false, "internal error: try to insert duplicate iterator in key_image set"); + } else if (in.type() == typeid(TransactionInputMultisignature)) { + if (!keptByBlock) { + const auto& msig = boost::get(in); + auto r = m_spentOutputs.insert(GlobalOutput(msig.amount, msig.outputIndex)); + (void)r; + assert(r.second); + } + } + } + + return true; + } + + //--------------------------------------------------------------------------------- + bool tx_memory_pool::haveSpentInputs(const Transaction& tx) const { + for (const auto& in : tx.vin) { + if (in.type() == typeid(TransactionInputToKey)) { + const auto& tokey_in = boost::get(in); + if (m_spent_key_images.count(tokey_in.keyImage)) { + return true; + } + } else if (in.type() == typeid(TransactionInputMultisignature)) { + const auto& msig = boost::get(in); + if (m_spentOutputs.count(GlobalOutput(msig.amount, msig.outputIndex))) { + return true; + } + } + } + return false; + } } diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 3d0c3336..91db2ca7 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -21,35 +21,80 @@ #include #include #include + #include #include +// multi index +#include +#include +#include +#include + +// epee +#include "math_helper.h" #include "string_tools.h" #include "syncobj.h" -#include "cryptonote_basic_impl.h" -#include "verification_context.h" + +#include "common/util.h" +#include "common/int-util.h" #include "crypto/hash.h" +#include "cryptonote_core/cryptonote_basic_impl.h" +#include "cryptonote_core/Currency.h" +#include "cryptonote_core/ITimeProvider.h" +#include "cryptonote_core/ITransactionValidator.h" +#include "cryptonote_core/verification_context.h" namespace cryptonote { - class blockchain_storage; + + + class OnceInTimeInterval { + public: + OnceInTimeInterval(unsigned interval, CryptoNote::ITimeProvider& timeProvider) + : m_interval(interval), m_timeProvider(timeProvider) { + m_lastWorkedTime = 0; + } + + template + bool call(functor_t functr) { + time_t now = m_timeProvider.now(); + + if (now - m_lastWorkedTime > m_interval) { + bool res = functr(); + m_lastWorkedTime = m_timeProvider.now(); + return res; + } + + return true; + } + + private: + time_t m_lastWorkedTime; + unsigned m_interval; + CryptoNote::ITimeProvider& m_timeProvider; + }; + + using CryptoNote::BlockInfo; + using namespace boost::multi_index; /************************************************************************/ /* */ /************************************************************************/ class tx_memory_pool: boost::noncopyable { public: - tx_memory_pool(blockchain_storage& bchs); + tx_memory_pool(const cryptonote::Currency& currency, CryptoNote::ITransactionValidator& validator, + CryptoNote::ITimeProvider& timeProvider); // load/store operations bool init(const std::string& config_folder); bool deinit(); bool have_tx(const crypto::hash &id) const; - bool add_tx(const transaction &tx, const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block); - bool add_tx(const transaction &tx, tx_verification_context& tvc, bool keeped_by_block); + bool add_tx(const Transaction &tx, const crypto::hash &id, size_t blobSize, tx_verification_context& tvc, bool keeped_by_block); + bool add_tx(const Transaction &tx, tx_verification_context& tvc, bool keeped_by_block); //gets tx and remove it from pool - bool take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee); + bool take_tx(const crypto::hash &id, Transaction &tx, size_t& blobSize, uint64_t& fee); bool on_blockchain_inc(uint64_t new_block_height, const crypto::hash& top_block_id); bool on_blockchain_dec(uint64_t new_block_height, const crypto::hash& top_block_id); @@ -57,13 +102,28 @@ namespace cryptonote { void lock() const; void unlock() const; - bool fill_block_template(block &bl, size_t median_size, uint64_t already_generated_coins, size_t &total_size, uint64_t &fee); + bool fill_block_template(Block &bl, size_t median_size, size_t maxCumulativeSize, uint64_t already_generated_coins, size_t &total_size, uint64_t &fee); - void get_transactions(std::list& txs) const; + void get_transactions(std::list& txs) const; size_t get_transactions_count() const; std::string print_pool(bool short_format) const; + void on_idle(); -#define CURRENT_MEMPOOL_ARCHIVE_VER 7 + template + void getTransactions(const t_ids_container& txsIds, t_tx_container& txs, t_missed_container& missedTxs) { + CRITICAL_REGION_LOCAL(m_transactions_lock); + + for (const auto& id : txsIds) { + auto it = m_transactions.find(id); + if (it == m_transactions.end()) { + missedTxs.push_back(id); + } else { + txs.push_back(it->tx); + } + } + } + +#define CURRENT_MEMPOOL_ARCHIVE_VER 10 template void serialize(archive_t & a, const unsigned int version) { @@ -74,56 +134,79 @@ namespace cryptonote { CRITICAL_REGION_LOCAL(m_transactions_lock); a & m_transactions; a & m_spent_key_images; + a & m_spentOutputs; } - struct tx_details { - transaction tx; - size_t blob_size; + struct TransactionCheckInfo { + BlockInfo maxUsedBlock; + BlockInfo lastFailedBlock; + }; + + struct TransactionDetails : public TransactionCheckInfo { + crypto::hash id; + Transaction tx; + size_t blobSize; uint64_t fee; - crypto::hash max_used_block_id; - uint64_t max_used_block_height; - bool kept_by_block; - // - uint64_t last_failed_height; - crypto::hash last_failed_id; + bool keptByBlock; + time_t receiveTime; }; private: - bool have_tx_keyimg_as_spent(const crypto::key_image& key_im) const; - bool have_tx_keyimges_as_spent(const transaction& tx) const; - bool remove_transaction_keyimages(const transaction& tx); - static bool have_key_images(const std::unordered_set& kic, const transaction& tx); - static bool append_key_images(std::unordered_set& kic, const transaction& tx); - bool is_transaction_ready_to_go(tx_details& txd) const; + struct TransactionPriorityComparator { + // lhs > hrs + bool operator()(const TransactionDetails& lhs, const TransactionDetails& rhs) const { + // price(lhs) = lhs.fee / lhs.blobSize + // price(lhs) > price(rhs) --> + // lhs.fee / lhs.blobSize > rhs.fee / rhs.blobSize --> + // lhs.fee * rhs.blobSize > rhs.fee * lhs.blobSize + uint64_t lhs_hi, lhs_lo = mul128(lhs.fee, rhs.blobSize, &lhs_hi); + uint64_t rhs_hi, rhs_lo = mul128(rhs.fee, lhs.blobSize, &rhs_hi); - typedef std::unordered_map transactions_container; + return + // prefer more profitable transactions + (lhs_hi > rhs_hi) || + (lhs_hi == rhs_hi && lhs_lo > rhs_lo) || + // prefer smaller + (lhs_hi == rhs_hi && lhs_lo == rhs_lo && lhs.blobSize < rhs.blobSize) || + // prefer older + (lhs_hi == rhs_hi && lhs_lo == rhs_lo && lhs.blobSize == rhs.blobSize && lhs.receiveTime < rhs.receiveTime); + } + }; + + typedef hashed_unique main_index_t; + typedef ordered_non_unique, TransactionPriorityComparator> fee_index_t; + + typedef multi_index_container + > tx_container_t; + + typedef std::pair GlobalOutput; + typedef std::set GlobalOutputsContainer; typedef std::unordered_map > key_images_container; + + // double spending checking + bool addTransactionInputs(const crypto::hash& id, const Transaction& tx, bool keptByBlock); + bool haveSpentInputs(const Transaction& tx) const; + bool removeTransactionInputs(const crypto::hash& id, const Transaction& tx, bool keptByBlock); + + tx_container_t::iterator removeTransaction(tx_container_t::iterator i); + bool removeExpiredTransactions(); + bool is_transaction_ready_to_go(const Transaction& tx, TransactionCheckInfo& txd) const; + + const cryptonote::Currency& m_currency; + OnceInTimeInterval m_txCheckInterval; mutable epee::critical_section m_transactions_lock; - transactions_container m_transactions; key_images_container m_spent_key_images; + GlobalOutputsContainer m_spentOutputs; std::string m_config_folder; - blockchain_storage& m_blockchain; + CryptoNote::ITransactionValidator& m_validator; + CryptoNote::ITimeProvider& m_timeProvider; - /************************************************************************/ - /* */ - /************************************************************************/ - class amount_visitor: public boost::static_visitor { - public: - uint64_t operator()(const txin_to_key& tx) const { - return tx.amount; - } - - uint64_t operator()(const txin_gen& tx) const { - CHECK_AND_ASSERT_MES(false, false, "coinbase transaction in memory pool"); - return 0; - } - - uint64_t operator()(const txin_to_script& tx) const { return 0; } - uint64_t operator()(const txin_to_scripthash& tx) const { return 0; } - }; + tx_container_t m_transactions; + tx_container_t::nth_index<1>::type& m_fee_index; #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) friend class blockchain_storage; @@ -134,14 +217,17 @@ namespace cryptonote { namespace boost { namespace serialization { template - void serialize(archive_t & ar, cryptonote::tx_memory_pool::tx_details& td, const unsigned int version) { - ar & td.blob_size; + void serialize(archive_t & ar, cryptonote::tx_memory_pool::TransactionDetails& td, const unsigned int version) { + ar & td.id; + ar & td.blobSize; ar & td.fee; ar & td.tx; - ar & td.max_used_block_height; - ar & td.max_used_block_id; - ar & td.last_failed_height; - ar & td.last_failed_id; + ar & td.maxUsedBlock.height; + ar & td.maxUsedBlock.id; + ar & td.lastFailedBlock.height; + ar & td.lastFailedBlock.id; + ar & td.keptByBlock; + ar & td.receiveTime; } } } diff --git a/src/cryptonote_core/verification_context.h b/src/cryptonote_core/verification_context.h index 44bcc151..52fd1cc1 100644 --- a/src/cryptonote_core/verification_context.h +++ b/src/cryptonote_core/verification_context.h @@ -27,6 +27,7 @@ namespace cryptonote bool m_verifivation_failed; //bad tx, should drop connection bool m_verifivation_impossible; //the transaction is related with an alternative blockchain bool m_added_to_pool; + bool m_tx_fee_too_small; }; struct block_verification_context diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h index 117bd67d..7e28e89d 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.h +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h @@ -45,77 +45,82 @@ namespace cryptonote /************************************************************************/ /* */ /************************************************************************/ + struct NOTIFY_NEW_BLOCK_request + { + block_complete_entry b; + uint64_t current_blockchain_height; + uint32_t hop; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(b) + KV_SERIALIZE(current_blockchain_height) + KV_SERIALIZE(hop) + END_KV_SERIALIZE_MAP() + }; + struct NOTIFY_NEW_BLOCK { const static int ID = BC_COMMANDS_POOL_BASE + 1; - - struct request - { - block_complete_entry b; - uint64_t current_blockchain_height; - uint32_t hop; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(b) - KV_SERIALIZE(current_blockchain_height) - KV_SERIALIZE(hop) - END_KV_SERIALIZE_MAP() - }; + typedef NOTIFY_NEW_BLOCK_request request; }; /************************************************************************/ /* */ /************************************************************************/ + struct NOTIFY_NEW_TRANSACTIONS_request + { + std::list txs; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txs) + END_KV_SERIALIZE_MAP() + }; + struct NOTIFY_NEW_TRANSACTIONS { const static int ID = BC_COMMANDS_POOL_BASE + 2; - - struct request - { - std::list txs; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(txs) - END_KV_SERIALIZE_MAP() - }; + typedef NOTIFY_NEW_TRANSACTIONS_request request; }; + /************************************************************************/ /* */ /************************************************************************/ + struct NOTIFY_REQUEST_GET_OBJECTS_request + { + std::list txs; + std::list blocks; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(txs) + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(blocks) + END_KV_SERIALIZE_MAP() + }; + struct NOTIFY_REQUEST_GET_OBJECTS { const static int ID = BC_COMMANDS_POOL_BASE + 3; + typedef NOTIFY_REQUEST_GET_OBJECTS_request request; + }; - struct request - { - std::list txs; - std::list blocks; + struct NOTIFY_RESPONSE_GET_OBJECTS_request + { + std::list txs; + std::list blocks; + std::list missed_ids; + uint64_t current_blockchain_height; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE_CONTAINER_POD_AS_BLOB(txs) - KV_SERIALIZE_CONTAINER_POD_AS_BLOB(blocks) - END_KV_SERIALIZE_MAP() - }; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txs) + KV_SERIALIZE(blocks) + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(missed_ids) + KV_SERIALIZE(current_blockchain_height) + END_KV_SERIALIZE_MAP() }; struct NOTIFY_RESPONSE_GET_OBJECTS { const static int ID = BC_COMMANDS_POOL_BASE + 4; - - struct request - { - std::list txs; - std::list blocks; - std::list missed_ids; - uint64_t current_blockchain_height; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(txs) - KV_SERIALIZE(blocks) - KV_SERIALIZE_CONTAINER_POD_AS_BLOB(missed_ids) - KV_SERIALIZE(current_blockchain_height) - END_KV_SERIALIZE_MAP() - }; + typedef NOTIFY_RESPONSE_GET_OBJECTS_request request; }; @@ -144,22 +149,23 @@ namespace cryptonote }; }; + struct NOTIFY_RESPONSE_CHAIN_ENTRY_request + { + uint64_t start_height; + uint64_t total_height; + std::list m_block_ids; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(start_height) + KV_SERIALIZE(total_height) + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_block_ids) + END_KV_SERIALIZE_MAP() + }; + struct NOTIFY_RESPONSE_CHAIN_ENTRY { const static int ID = BC_COMMANDS_POOL_BASE + 7; - - struct request - { - uint64_t start_height; - uint64_t total_height; - std::list m_block_ids; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(start_height) - KV_SERIALIZE(total_height) - KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_block_ids) - END_KV_SERIALIZE_MAP() - }; + typedef NOTIFY_RESPONSE_CHAIN_ENTRY_request request; }; } diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 8b53121d..bcbcf968 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -123,7 +123,7 @@ namespace cryptonote int64_t diff = static_cast(hshd.current_height) - static_cast(m_core.get_current_blockchain_height()); LOG_PRINT_CCONTEXT_YELLOW("Sync data returned unknown top block: " << m_core.get_current_blockchain_height() << " -> " << hshd.current_height - << " [" << std::abs(diff) << " blocks (" << diff / (24 * 60 * 60 / DIFFICULTY_TARGET) << " days) " + << " [" << std::abs(diff) << " blocks (" << diff / (24 * 60 * 60 / m_core.currency().difficultyTarget()) << " days) " << (0 <= diff ? std::string("behind") : std::string("ahead")) << "] " << ENDL << "SYNCHRONIZATION started", (is_inital ? LOG_LEVEL_0:LOG_LEVEL_1)); LOG_PRINT_L1("Remote top block height: " << hshd.current_height << ", id: " << hshd.top_id); @@ -153,50 +153,42 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------ - template - int t_cryptonote_protocol_handler::handle_notify_new_block(int command, NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& context) - { + template + int t_cryptonote_protocol_handler::handle_notify_new_block(int command, NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& context) { LOG_PRINT_CCONTEXT_L2("NOTIFY_NEW_BLOCK (hop " << arg.hop << ")"); - if(context.m_state != cryptonote_connection_context::state_normal) + if (context.m_state != cryptonote_connection_context::state_normal) { return 1; + } - for(auto tx_blob_it = arg.b.txs.begin(); tx_blob_it!=arg.b.txs.end();tx_blob_it++) - { + for (auto tx_blob_it = arg.b.txs.begin(); tx_blob_it != arg.b.txs.end(); tx_blob_it++) { cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); m_core.handle_incoming_tx(*tx_blob_it, tvc, true); - if(tvc.m_verifivation_failed) - { + if (tvc.m_verifivation_failed) { LOG_PRINT_CCONTEXT_L0("Block verification failed: transaction verification failed, dropping connection"); m_p2p->drop_connection(context); return 1; } } - block_verification_context bvc = boost::value_initialized(); - m_core.pause_mine(); - m_core.handle_incoming_block(arg.b.block, bvc); - m_core.resume_mine(); - if(bvc.m_verifivation_failed) - { + m_core.handle_incoming_block_blob(arg.b.block, bvc, true, false); + if (bvc.m_verifivation_failed) { LOG_PRINT_CCONTEXT_L0("Block verification failed, dropping connection"); m_p2p->drop_connection(context); return 1; } - if(bvc.m_added_to_main_chain) - { + if (bvc.m_added_to_main_chain) { ++arg.hop; //TODO: Add here announce protocol usage relay_block(arg, context); - }else if(bvc.m_marked_as_orphaned) - { + } else if (bvc.m_marked_as_orphaned) { context.m_state = cryptonote_connection_context::state_synchronizing; NOTIFY_REQUEST_CHAIN::request r = boost::value_initialized(); m_core.get_short_chain_history(r.block_ids); - LOG_PRINT_CCONTEXT_L2("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() ); + LOG_PRINT_CCONTEXT_L2("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size()); post_notify(r, context); } - + return 1; } //------------------------------------------------------------------------------------------------------------------------ @@ -266,14 +258,14 @@ namespace cryptonote BOOST_FOREACH(const block_complete_entry& block_entry, arg.blocks) { ++count; - block b; + Block b; if(!parse_and_validate_block_from_blob(block_entry.block, b)) { LOG_ERROR_CCONTEXT("sent wrong block: failed to parse and validate block: \r\n" << epee::string_tools::buff_to_hex_nodelimer(block_entry.block) << "\r\n dropping connection"); m_p2p->drop_connection(context); return 1; - } + } //to avoid concurrency in core between connections, suspend connections which delivered block later then first one if(count == 2) { @@ -286,7 +278,7 @@ namespace cryptonote return 1; } } - + auto req_it = context.m_requested_objects.find(get_block_hash(b)); if(req_it == context.m_requested_objects.end()) { @@ -295,10 +287,10 @@ namespace cryptonote m_p2p->drop_connection(context); return 1; } - if(b.tx_hashes.size() != block_entry.txs.size()) + if (b.txHashes.size() != block_entry.txs.size()) { LOG_ERROR_CCONTEXT("sent wrong NOTIFY_RESPONSE_GET_OBJECTS: block with id=" << epee::string_tools::pod_to_hex(get_blob_hash(block_entry.block)) - << ", tx_hashes.size()=" << b.tx_hashes.size() << " mismatch with block_complete_entry.m_txs.size()=" << block_entry.txs.size() << ", dropping connection"); + << ", txHashes.size()=" << b.txHashes.size() << " mismatch with block_complete_entry.m_txs.size()=" << block_entry.txs.size() << ", dropping connection"); m_p2p->drop_connection(context); return 1; } @@ -315,20 +307,17 @@ namespace cryptonote } { - m_core.pause_mine(); + m_core.pause_mining(); epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler( - boost::bind(&t_core::resume_mine, &m_core)); + boost::bind(&t_core::update_block_template_and_resume_mining, &m_core)); - BOOST_FOREACH(const block_complete_entry& block_entry, arg.blocks) - { + for (const block_complete_entry& block_entry : arg.blocks) { //process transactions TIME_MEASURE_START(transactions_process_time); - BOOST_FOREACH(auto& tx_blob, block_entry.txs) - { + for (auto& tx_blob : block_entry.txs) { tx_verification_context tvc = AUTO_VAL_INIT(tvc); m_core.handle_incoming_tx(tx_blob, tvc, true); - if(tvc.m_verifivation_failed) - { + if (tvc.m_verifivation_failed) { LOG_ERROR_CCONTEXT("transaction verification failed on NOTIFY_RESPONSE_GET_OBJECTS, \r\ntx_id = " << epee::string_tools::pod_to_hex(get_blob_hash(tx_blob)) << ", dropping connection"); m_p2p->drop_connection(context); @@ -340,24 +329,21 @@ namespace cryptonote //process block TIME_MEASURE_START(block_process_time); block_verification_context bvc = boost::value_initialized(); + m_core.handle_incoming_block_blob(block_entry.block, bvc, false, false); - m_core.handle_incoming_block(block_entry.block, bvc, false); - - if(bvc.m_verifivation_failed) - { + if (bvc.m_verifivation_failed) { LOG_PRINT_CCONTEXT_L0("Block verification failed, dropping connection"); m_p2p->drop_connection(context); return 1; - } - if(bvc.m_marked_as_orphaned) - { + } else if (bvc.m_marked_as_orphaned) { LOG_PRINT_CCONTEXT_L0("Block received at sync phase was marked as orphaned, dropping connection"); m_p2p->drop_connection(context); return 1; } TIME_MEASURE_FINISH(block_process_time); - LOG_PRINT_CCONTEXT_L2("Block process time: " << block_process_time + transactions_process_time << "(" << transactions_process_time << "/" << block_process_time << ")ms"); + LOG_PRINT_CCONTEXT_L2("Block process time: " << block_process_time + transactions_process_time << + " (" << transactions_process_time << " / " << block_process_time << ") ms"); } } diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler_common.h b/src/cryptonote_protocol/cryptonote_protocol_handler_common.h index dcd7dad3..f85d512f 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler_common.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler_common.h @@ -18,33 +18,32 @@ #pragma once #include "p2p/net_node_common.h" -#include "cryptonote_protocol/cryptonote_protocol_defs.h" #include "cryptonote_core/connection_context.h" + namespace cryptonote { + struct NOTIFY_NEW_BLOCK_request; + struct NOTIFY_NEW_TRANSACTIONS_request; + /************************************************************************/ /* */ /************************************************************************/ - struct i_cryptonote_protocol - { - virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context)=0; - virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context)=0; + struct i_cryptonote_protocol { + virtual bool relay_block(NOTIFY_NEW_BLOCK_request& arg, cryptonote_connection_context& exclude_context)=0; + virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS_request& arg, cryptonote_connection_context& exclude_context)=0; //virtual bool request_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote_connection_context& context)=0; }; /************************************************************************/ /* */ /************************************************************************/ - struct cryptonote_protocol_stub: public i_cryptonote_protocol - { - virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context) - { - return false; - } - virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context) - { + struct cryptonote_protocol_stub: public i_cryptonote_protocol { + virtual bool relay_block(NOTIFY_NEW_BLOCK_request& arg, cryptonote_connection_context& exclude_context) { return false; } + virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS_request& arg, cryptonote_connection_context& exclude_context) { + return false; + } }; } diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index d3465b49..3881b64c 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -26,14 +26,17 @@ using namespace epee; #include -#include "crypto/hash.h" +// epee #include "console_handler.h" -#include "p2p/net_node.h" -#include "cryptonote_core/checkpoints_create.h" + +#include "common/SignalHandler.h" +#include "crypto/hash.h" #include "cryptonote_core/cryptonote_core.h" -#include "rpc/core_rpc_server.h" +#include "cryptonote_core/Currency.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" -#include "daemon_commands_handler.h" +#include "daemon/daemon_commands_handler.h" +#include "p2p/net_node.h" +#include "rpc/core_rpc_server.h" #include "version.h" #if defined(WIN32) @@ -44,11 +47,13 @@ namespace po = boost::program_options; namespace { - const command_line::arg_descriptor arg_config_file = {"config-file", "Specify configuration file", std::string(CRYPTONOTE_NAME ".conf")}; + const command_line::arg_descriptor arg_config_file = {"config-file", "Specify configuration file", std::string(cryptonote::CRYPTONOTE_NAME) + ".conf"}; const command_line::arg_descriptor arg_os_version = {"os-version", ""}; const command_line::arg_descriptor arg_log_file = {"log-file", "", ""}; const command_line::arg_descriptor arg_log_level = {"log-level", "", LOG_LEVEL_0}; const command_line::arg_descriptor arg_console = {"no-console", "Disable daemon console commands"}; + const command_line::arg_descriptor arg_testnet_on = {"testnet", "Used to deploy test nets. Checkpoints and hardcoded seeds are ignored, " + "network id is changed. Use it with --data-dir flag. The wallet must be launched with --testnet flag.", false}; } bool command_line_preprocessor(const boost::program_options::variables_map& vm); @@ -79,7 +84,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_cmd_sett, arg_log_file); command_line::add_arg(desc_cmd_sett, arg_log_level); command_line::add_arg(desc_cmd_sett, arg_console); - + command_line::add_arg(desc_cmd_sett, arg_testnet_on); cryptonote::core::init_options(desc_cmd_sett); cryptonote::core_rpc_server::init_options(desc_cmd_sett); @@ -96,7 +101,7 @@ int main(int argc, char* argv[]) if (command_line::get_arg(vm, command_line::arg_help)) { - std::cout << CRYPTONOTE_NAME << " v" << PROJECT_VERSION_LONG << ENDL << ENDL; + std::cout << cryptonote::CRYPTONOTE_NAME << " v" << PROJECT_VERSION_LONG << ENDL << ENDL; std::cout << desc_options << std::endl; return false; } @@ -131,7 +136,7 @@ int main(int argc, char* argv[]) log_dir = log_file_path.has_parent_path() ? log_file_path.parent_path().string() : log_space::log_singletone::get_default_log_folder(); log_space::log_singletone::add_logger(LOGGER_FILE, log_file_path.filename().string().c_str(), log_dir.c_str()); - LOG_PRINT_L0(CRYPTONOTE_NAME << " v" << PROJECT_VERSION_LONG); + LOG_PRINT_L0(cryptonote::CRYPTONOTE_NAME << " v" << PROJECT_VERSION_LONG); if (command_line_preprocessor(vm)) { @@ -140,14 +145,26 @@ int main(int argc, char* argv[]) LOG_PRINT("Module folder: " << argv[0], LOG_LEVEL_0); - bool res = true; - cryptonote::checkpoints checkpoints; - res = cryptonote::create_checkpoints(checkpoints); - CHECK_AND_ASSERT_MES(res, 1, "Failed to initialize checkpoints"); + bool testnet_mode = command_line::get_arg(vm, arg_testnet_on); + if (testnet_mode) { + LOG_PRINT_L0("Starting in testnet mode!"); + } //create objects and link them - cryptonote::core ccore(NULL); - ccore.set_checkpoints(std::move(checkpoints)); + cryptonote::CurrencyBuilder currencyBuilder; + currencyBuilder.testnet(testnet_mode); + cryptonote::Currency currency = currencyBuilder.currency(); + cryptonote::core ccore(currency, NULL); + + cryptonote::checkpoints checkpoints; + for (const auto& cp : cryptonote::CHECKPOINTS) { + checkpoints.add_checkpoint(cp.height, cp.blockId); + } + + if (!testnet_mode) { + ccore.set_checkpoints(std::move(checkpoints)); + } + cryptonote::t_cryptonote_protocol_handler cprotocol(ccore, NULL); nodetool::node_server > p2psrv(cprotocol); cryptonote::core_rpc_server rpc_server(ccore, p2psrv); @@ -157,7 +174,7 @@ int main(int argc, char* argv[]) //initialize objects LOG_PRINT_L0("Initializing p2p server..."); - res = p2psrv.init(vm); + bool res = p2psrv.init(vm, testnet_mode); CHECK_AND_ASSERT_MES(res, 1, "Failed to initialize p2p server."); LOG_PRINT_L0("P2p server initialized OK"); @@ -188,7 +205,7 @@ int main(int argc, char* argv[]) CHECK_AND_ASSERT_MES(res, 1, "Failed to initialize core rpc server."); LOG_PRINT_L0("Core rpc server started ok"); - tools::signal_handler::install([&dch, &p2psrv] { + tools::SignalHandler::install([&dch, &p2psrv] { dch.stop_handling(); p2psrv.send_stop_signal(); }); @@ -227,7 +244,7 @@ bool command_line_preprocessor(const boost::program_options::variables_map& vm) bool exit = false; if (command_line::get_arg(vm, command_line::arg_version)) { - std::cout << CRYPTONOTE_NAME << " v" << PROJECT_VERSION_LONG << ENDL; + std::cout << cryptonote::CRYPTONOTE_NAME << " v" << PROJECT_VERSION_LONG << ENDL; exit = true; } if (command_line::get_arg(vm, arg_os_version)) diff --git a/src/daemon/daemon_commands_handler.h b/src/daemon/daemon_commands_handler.h index 7a6625ae..837839aa 100644 --- a/src/daemon/daemon_commands_handler.h +++ b/src/daemon/daemon_commands_handler.h @@ -21,6 +21,7 @@ #include "console_handler.h" #include "p2p/net_node.h" +#include "cryptonote_core/miner.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" #include "common/util.h" #include "crypto/hash.h" @@ -47,7 +48,6 @@ public: m_cmd_binder.set_handler("print_pool_sh", boost::bind(&daemon_cmmands_handler::print_pool_sh, this, _1), "Print transaction pool (short format)"); m_cmd_binder.set_handler("show_hr", boost::bind(&daemon_cmmands_handler::show_hr, this, _1), "Start showing hash rate"); m_cmd_binder.set_handler("hide_hr", boost::bind(&daemon_cmmands_handler::hide_hr, this, _1), "Stop showing hash rate"); - m_cmd_binder.set_handler("save", boost::bind(&daemon_cmmands_handler::save, this, _1), "Save blockchain"); m_cmd_binder.set_handler("set_log", boost::bind(&daemon_cmmands_handler::set_log, this, _1), "set_log - Change current log detalization level, is a number 0-4"); } @@ -69,7 +69,7 @@ private: std::string get_commands_str() { std::stringstream ss; - ss << CRYPTONOTE_NAME << " v" << PROJECT_VERSION_LONG << ENDL; + ss << cryptonote::CRYPTONOTE_NAME << " v" << PROJECT_VERSION_LONG << ENDL; ss << "Commands: " << ENDL; std::string usage = m_cmd_binder.get_usage(); boost::replace_all(usage, "\n", "\n "); @@ -90,12 +90,6 @@ private: return true; } //-------------------------------------------------------------------------------- - bool save(const std::vector& args) - { - m_srv.get_payload_object().get_core().get_blockchain_storage().store_blockchain(); - return true; - } - //-------------------------------------------------------------------------------- bool show_hr(const std::vector& args) { if(!m_srv.get_payload_object().get_core().get_miner().is_mining()) @@ -204,22 +198,20 @@ private: //-------------------------------------------------------------------------------- template - static bool print_as_json(T& obj) - { + static bool print_as_json(const T& obj) { std::cout << cryptonote::obj_to_json_str(obj) << ENDL; return true; } //-------------------------------------------------------------------------------- bool print_block_by_height(uint64_t height) { - std::list blocks; + std::list blocks; m_srv.get_payload_object().get_core().get_blocks(height, 1, blocks); if (1 == blocks.size()) { - cryptonote::block& block = blocks.front(); - std::cout << "block_id: " << get_block_hash(block) << ENDL; - print_as_json(block); + std::cout << "block_id: " << get_block_hash(blocks.front()) << ENDL; + print_as_json(blocks.front()); } else { @@ -243,14 +235,13 @@ private: std::list block_ids; block_ids.push_back(block_hash); - std::list blocks; + std::list blocks; std::list missed_ids; m_srv.get_payload_object().get_core().get_blocks(block_ids, blocks, missed_ids); if (1 == blocks.size()) { - cryptonote::block block = blocks.front(); - print_as_json(block); + print_as_json(blocks.front()); } else { @@ -300,14 +291,13 @@ private: std::vector tx_ids; tx_ids.push_back(tx_hash); - std::list txs; + std::list txs; std::list missed_ids; m_srv.get_payload_object().get_core().get_transactions(tx_ids, txs, missed_ids); if (1 == txs.size()) { - cryptonote::transaction tx = txs.front(); - print_as_json(tx); + print_as_json(txs.front()); } else { @@ -336,8 +326,8 @@ private: return true; } - cryptonote::account_public_address adr; - if(!cryptonote::get_account_address_from_str(adr, args.front())) + cryptonote::AccountPublicAddress adr; + if(!m_srv.get_payload_object().get_core().currency().parseAccountAddressString(args.front(), adr)) { std::cout << "target account address has wrong format" << std::endl; return true; @@ -350,7 +340,7 @@ private: } boost::thread::attributes attrs; - attrs.set_stack_size(THREAD_STACK_SIZE); + attrs.set_stack_size(cryptonote::THREAD_STACK_SIZE); m_srv.get_payload_object().get_core().get_miner().start(adr, threads_count, attrs); return true; diff --git a/src/node_rpc_proxy/NodeRpcProxy.cpp b/src/node_rpc_proxy/NodeRpcProxy.cpp index c024a8b7..1e434d9d 100644 --- a/src/node_rpc_proxy/NodeRpcProxy.cpp +++ b/src/node_rpc_proxy/NodeRpcProxy.cpp @@ -181,7 +181,7 @@ uint64_t NodeRpcProxy::getLastKnownBlockHeight() const { return m_networkHeight; } -void NodeRpcProxy::relayTransaction(const cryptonote::transaction& transaction, const Callback& callback) { +void NodeRpcProxy::relayTransaction(const cryptonote::Transaction& transaction, const Callback& callback) { if (!m_initState.initialized()) { callback(make_error_code(error::NOT_INITIALIZED)); return; @@ -218,7 +218,7 @@ void NodeRpcProxy::getTransactionOutsGlobalIndices(const crypto::hash& transacti m_ioService.post(std::bind(&NodeRpcProxy::doGetTransactionOutsGlobalIndices, this, transactionHash, std::ref(outsGlobalIndices), callback)); } -void NodeRpcProxy::doRelayTransaction(const cryptonote::transaction& transaction, const Callback& callback) { +void NodeRpcProxy::doRelayTransaction(const cryptonote::Transaction& transaction, const Callback& callback) { COMMAND_RPC_SEND_RAW_TX::request req; COMMAND_RPC_SEND_RAW_TX::response rsp; req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(cryptonote::tx_to_blob(transaction)); diff --git a/src/node_rpc_proxy/NodeRpcProxy.h b/src/node_rpc_proxy/NodeRpcProxy.h index db5faaa5..158fdf1e 100644 --- a/src/node_rpc_proxy/NodeRpcProxy.h +++ b/src/node_rpc_proxy/NodeRpcProxy.h @@ -45,7 +45,7 @@ public: virtual uint64_t getLastLocalBlockHeight() const; virtual uint64_t getLastKnownBlockHeight() const; - virtual void relayTransaction(const cryptonote::transaction& transaction, const Callback& callback); + virtual void relayTransaction(const cryptonote::Transaction& transaction, const Callback& callback); virtual void getRandomOutsByAmounts(std::vector&& amounts, uint64_t outsCount, std::vector& result, const Callback& callback); virtual void getNewBlocks(std::list&& knownBlockIds, std::list& newBlocks, uint64_t& startHeight, const Callback& callback); virtual void getTransactionOutsGlobalIndices(const crypto::hash& transactionHash, std::vector& outsGlobalIndices, const Callback& callback); @@ -61,7 +61,7 @@ private: void updateNodeStatus(); void updatePeerCount(); - void doRelayTransaction(const cryptonote::transaction& transaction, const Callback& callback); + void doRelayTransaction(const cryptonote::Transaction& transaction, const Callback& callback); void doGetRandomOutsByAmounts(std::vector& amounts, uint64_t outsCount, std::vector& result, const Callback& callback); void doGetNewBlocks(std::list& knownBlockIds, std::list& newBlocks, uint64_t& startHeight, const Callback& callback); void doGetTransactionOutsGlobalIndices(const crypto::hash& transactionHash, std::vector& outsGlobalIndices, const Callback& callback); diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index e844b429..a8356eaf 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -66,13 +66,13 @@ namespace nodetool public: typedef t_payload_net_handler payload_net_handler; // Some code - node_server(t_payload_net_handler& payload_handler):m_payload_handler(payload_handler), m_allow_local_ip(false), m_hide_my_port(false) + node_server(t_payload_net_handler& payload_handler):m_payload_handler(payload_handler), m_allow_local_ip(false), m_hide_my_port(false), m_network_id(BYTECOIN_NETWORK) {} static void init_options(boost::program_options::options_description& desc); bool run(); - bool init(const boost::program_options::variables_map& vm); + bool init(const boost::program_options::variables_map& vm, bool testnet); bool deinit(); bool send_stop_signal(); uint32_t get_this_peer_port(){return m_listenning_port;} @@ -200,7 +200,7 @@ namespace nodetool t_payload_net_handler& m_payload_handler; peerlist_manager m_peerlist; - epee::math_helper::once_a_time_seconds m_peer_handshake_idle_maker_interval; + epee::math_helper::once_a_time_seconds m_peer_handshake_idle_maker_interval; epee::math_helper::once_a_time_seconds<1> m_connections_maker_interval; epee::math_helper::once_a_time_seconds<60*30, false> m_peerlist_store_interval; @@ -216,6 +216,7 @@ namespace nodetool uint64_t m_peer_livetime; //keep connections to initiate some interactions net_server m_net_server; + boost::uuids::uuid m_network_id; }; } diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index d56a8351..87346fdb 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -40,7 +40,7 @@ namespace nodetool namespace { const command_line::arg_descriptor arg_p2p_bind_ip = {"p2p-bind-ip", "Interface for p2p network protocol", "0.0.0.0"}; - const command_line::arg_descriptor arg_p2p_bind_port = {"p2p-bind-port", "Port for p2p network protocol", boost::to_string(P2P_DEFAULT_PORT)}; + const command_line::arg_descriptor arg_p2p_bind_port = {"p2p-bind-port", "Port for p2p network protocol", boost::to_string(cryptonote::P2P_DEFAULT_PORT)}; const command_line::arg_descriptor arg_p2p_external_port = {"p2p-external-port", "External port for p2p network protocol (if port forwarding used with NAT)", 0}; const command_line::arg_descriptor arg_p2p_allow_local_ip = {"allow-local-ip", "Allow local ip add to peer list, mostly in debug purposes"}; const command_line::arg_descriptor > arg_p2p_add_peer = {"add-peer", "Manually add peer to local peerlist"}; @@ -63,14 +63,15 @@ namespace nodetool command_line::add_arg(desc, arg_p2p_add_priority_node); command_line::add_arg(desc, arg_p2p_add_exclusive_node); command_line::add_arg(desc, arg_p2p_seed_node); - command_line::add_arg(desc, arg_p2p_hide_my_port); } + command_line::add_arg(desc, arg_p2p_hide_my_port); + } //----------------------------------------------------------------------------------- template bool node_server::init_config() { // TRY_ENTRY(); - std::string state_file_path = m_config_folder + "/" + P2P_NET_DATA_FILENAME; + std::string state_file_path = m_config_folder + "/" + cryptonote::parameters::P2P_NET_DATA_FILENAME; std::ifstream p2p_data; p2p_data.open( state_file_path , std::ios_base::binary | std::ios_base::in); if(!p2p_data.fail()) @@ -83,13 +84,13 @@ namespace nodetool } //at this moment we have hardcoded config - m_config.m_net_config.handshake_interval = P2P_DEFAULT_HANDSHAKE_INTERVAL; - m_config.m_net_config.connections_count = P2P_DEFAULT_CONNECTIONS_COUNT; - m_config.m_net_config.packet_max_size = P2P_DEFAULT_PACKET_MAX_SIZE; //20 MB limit + m_config.m_net_config.handshake_interval = cryptonote::P2P_DEFAULT_HANDSHAKE_INTERVAL; + m_config.m_net_config.connections_count = cryptonote::P2P_DEFAULT_CONNECTIONS_COUNT; + m_config.m_net_config.packet_max_size = cryptonote::P2P_DEFAULT_PACKET_MAX_SIZE; //20 MB limit m_config.m_net_config.config_id = 0; // initial config - m_config.m_net_config.connection_timeout = P2P_DEFAULT_CONNECTION_TIMEOUT; - m_config.m_net_config.ping_connection_timeout = P2P_DEFAULT_PING_CONNECTION_TIMEOUT; - m_config.m_net_config.send_peerlist_sz = P2P_DEFAULT_PEERS_IN_HANDSHAKE; + m_config.m_net_config.connection_timeout = cryptonote::P2P_DEFAULT_CONNECTION_TIMEOUT; + m_config.m_net_config.ping_connection_timeout = cryptonote::P2P_DEFAULT_PING_CONNECTION_TIMEOUT; + m_config.m_net_config.send_peerlist_sz = cryptonote::P2P_DEFAULT_PEERS_IN_HANDSHAKE; m_first_connection_maker_call = true; CATCH_ENTRY_L0("node_server::init_config", false); @@ -201,21 +202,16 @@ namespace nodetool } } - #define ADD_HARDCODED_SEED_NODE(addr) append_net_address(m_seed_nodes, addr); //----------------------------------------------------------------------------------- template - bool node_server::init(const boost::program_options::variables_map& vm) - { - ADD_HARDCODED_SEED_NODE("seed.bytecoin.org:8080"); - ADD_HARDCODED_SEED_NODE("85.25.201.95:8080"); - ADD_HARDCODED_SEED_NODE("85.25.196.145:8080"); - ADD_HARDCODED_SEED_NODE("85.25.196.146:8080"); - ADD_HARDCODED_SEED_NODE("85.25.196.144:8080"); - ADD_HARDCODED_SEED_NODE("5.199.168.138:8080"); - ADD_HARDCODED_SEED_NODE("62.75.236.152:8080"); - ADD_HARDCODED_SEED_NODE("85.25.194.245:8080"); - ADD_HARDCODED_SEED_NODE("95.211.224.160:8080"); - ADD_HARDCODED_SEED_NODE("144.76.200.44:8080"); + bool node_server::init(const boost::program_options::variables_map& vm, bool testnet) { + if (!testnet) { + for (auto seed : cryptonote::SEED_NODES) { + append_net_address(m_seed_nodes, seed); + } + } else { + m_network_id.data[0] += 1; + } bool res = handle_command_line(vm); CHECK_AND_ASSERT_MES(res, false, "Failed to handle command line"); @@ -239,7 +235,7 @@ namespace nodetool //configure self m_net_server.set_threads_prefix("P2P"); m_net_server.get_config_object().m_pcommands_handler = this; - m_net_server.get_config_object().m_invoke_timeout = P2P_DEFAULT_INVOKE_TIMEOUT; + m_net_server.get_config_object().m_invoke_timeout = cryptonote::P2P_DEFAULT_INVOKE_TIMEOUT; //try to bind LOG_PRINT_L0("Binding on " << m_bind_ip << ":" << m_port); @@ -264,7 +260,8 @@ namespace nodetool if (result == 1) { std::ostringstream portString; portString << m_listenning_port; - if (UPNP_AddPortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), portString.str().c_str(), lanAddress, CRYPTONOTE_NAME, "TCP", 0, "0") != 0) { + if (UPNP_AddPortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), + portString.str().c_str(), lanAddress, cryptonote::CRYPTONOTE_NAME, "TCP", 0, "0") != 0) { LOG_ERROR("UPNP_AddPortMapping failed."); } else { LOG_PRINT_GREEN("Added IGD port mapping.", LOG_LEVEL_0); @@ -301,7 +298,7 @@ namespace nodetool m_net_server.add_idle_handler(boost::bind(&t_payload_net_handler::on_idle, &m_payload_handler), 1000); boost::thread::attributes attrs; - attrs.set_stack_size(THREAD_STACK_SIZE); + attrs.set_stack_size(cryptonote::THREAD_STACK_SIZE); //go to loop LOG_PRINT("Run net_service loop( " << thrds_count << " threads)...", LOG_LEVEL_0); @@ -340,7 +337,7 @@ namespace nodetool return false; } - std::string state_file_path = m_config_folder + "/" + P2P_NET_DATA_FILENAME; + std::string state_file_path = m_config_folder + "/" + cryptonote::parameters::P2P_NET_DATA_FILENAME; std::ofstream p2p_data; p2p_data.open( state_file_path , std::ios_base::binary | std::ios_base::out| std::ios::trunc); if(p2p_data.fail()) @@ -353,8 +350,6 @@ namespace nodetool a << *this; return true; CATCH_ENTRY_L0("blockchain_storage::save", false); - - return true; } //----------------------------------------------------------------------------------- template @@ -389,7 +384,7 @@ namespace nodetool return; } - if(rsp.node_data.network_id != BYTECOIN_NETWORK) + if(rsp.node_data.network_id != m_network_id) { LOG_ERROR_CCONTEXT("COMMAND_HANDSHAKE Failed, wrong network! (" << epee::string_tools::get_str_from_guid_a(rsp.node_data.network_id) << "), closing connection."); return; @@ -424,7 +419,7 @@ namespace nodetool { LOG_PRINT_CCONTEXT_L1(" COMMAND_HANDSHAKE(AND CLOSE) INVOKED OK"); } - }, P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT); + }, cryptonote::P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT); if(r) { @@ -667,7 +662,7 @@ namespace nodetool if (!connect_to_peerlist(m_priority_peers)) return false; - size_t expected_white_connections = (m_config.m_net_config.connections_count*P2P_DEFAULT_WHITELIST_CONNECTIONS_PERCENT)/100; + size_t expected_white_connections = (m_config.m_net_config.connections_count * cryptonote::P2P_DEFAULT_WHITELIST_CONNECTIONS_PERCENT) / 100; size_t conn_count = get_outgoing_connections_count(); if(conn_count < m_config.m_net_config.connections_count) @@ -797,7 +792,7 @@ namespace nodetool node_data.my_port = m_external_port ? m_external_port : m_listenning_port; else node_data.my_port = 0; - node_data.network_id = BYTECOIN_NETWORK; + node_data.network_id = m_network_id; return true; } //----------------------------------------------------------------------------------- @@ -823,7 +818,7 @@ namespace nodetool return false; } crypto::public_key pk = AUTO_VAL_INIT(pk); - epee::string_tools::hex_to_pod(P2P_STAT_TRUSTED_PUB_KEY, pk); + epee::string_tools::hex_to_pod(cryptonote::P2P_STAT_TRUSTED_PUB_KEY, pk); crypto::hash h = tools::get_proof_of_trust_hash(tr); if(!crypto::check_signature(h, pk, tr.sign)) { @@ -1017,9 +1012,8 @@ namespace nodetool template int node_server::handle_handshake(int command, typename COMMAND_HANDSHAKE::request& arg, typename COMMAND_HANDSHAKE::response& rsp, p2p_connection_context& context) { - if(arg.node_data.network_id != BYTECOIN_NETWORK) + if(arg.node_data.network_id != m_network_id) { - LOG_PRINT_CCONTEXT_L0("WRONG NETWORK AGENT CONNECTED! id=" << epee::string_tools::get_str_from_guid_a(arg.node_data.network_id)); drop_connection(context); return 1; diff --git a/src/p2p/net_node_common.h b/src/p2p/net_node_common.h index d1380f89..d86c3609 100644 --- a/src/p2p/net_node_common.h +++ b/src/p2p/net_node_common.h @@ -19,13 +19,13 @@ #include #include "net/net_utils_base.h" -#include "p2p_protocol_defs.h" namespace nodetool { typedef boost::uuids::uuid uuid; typedef boost::uuids::uuid net_connection_id; + typedef uint64_t peerid_type; template struct i_p2p_endpoint diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h index a57a1c0e..ba4bc0c1 100644 --- a/src/p2p/net_peerlist.h +++ b/src/p2p/net_peerlist.h @@ -56,7 +56,7 @@ namespace nodetool size_t get_white_peers_count(){CRITICAL_REGION_LOCAL(m_peerlist_lock); return m_peers_white.size();} size_t get_gray_peers_count(){CRITICAL_REGION_LOCAL(m_peerlist_lock); return m_peers_gray.size();} bool merge_peerlist(const std::list& outer_bs); - bool get_peerlist_head(std::list& bs_head, uint32_t depth = P2P_DEFAULT_PEERS_IN_HANDSHAKE); + bool get_peerlist_head(std::list& bs_head, uint32_t depth = cryptonote::P2P_DEFAULT_PEERS_IN_HANDSHAKE); bool get_peerlist_full(std::list& pl_gray, std::list& pl_white); bool get_white_peer_by_index(peerlist_entry& p, size_t i); bool get_gray_peer_by_index(peerlist_entry& p, size_t i); @@ -193,7 +193,7 @@ namespace nodetool //-------------------------------------------------------------------------------------------------- inline void peerlist_manager::trim_white_peerlist() { - while(m_peers_gray.size() > P2P_LOCAL_GRAY_PEERLIST_LIMIT) + while(m_peers_gray.size() > cryptonote::P2P_LOCAL_GRAY_PEERLIST_LIMIT) { peers_indexed::index::type& sorted_index=m_peers_gray.get(); sorted_index.erase(sorted_index.begin()); @@ -202,7 +202,7 @@ namespace nodetool //-------------------------------------------------------------------------------------------------- inline void peerlist_manager::trim_gray_peerlist() { - while(m_peers_white.size() > P2P_LOCAL_WHITE_PEERLIST_LIMIT) + while(m_peers_white.size() > cryptonote::P2P_LOCAL_WHITE_PEERLIST_LIMIT) { peers_indexed::index::type& sorted_index=m_peers_white.get(); sorted_index.erase(sorted_index.begin()); diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index c0f6efee..46213dfb 100644 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -18,8 +18,12 @@ #pragma once #include + #include "serialization/keyvalue_serialization.h" #include "misc_language.h" +#include "string_tools.h" +#include "time_helper.h" + #include "cryptonote_config.h" #include "crypto/crypto.h" diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index da65ebc9..641fda8f 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -15,18 +15,19 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . -#include -#include "include_base_utils.h" -using namespace epee; - #include "core_rpc_server.h" -#include "common/command_line.h" -#include "cryptonote_core/cryptonote_format_utils.h" -#include "cryptonote_core/account.h" -#include "cryptonote_core/cryptonote_basic_impl.h" + +#include "include_base_utils.h" #include "misc_language.h" + +#include "common/command_line.h" #include "crypto/hash.h" -#include "core_rpc_server_error_codes.h" +#include "cryptonote_core/cryptonote_basic_impl.h" +#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_core/miner.h" +#include "rpc/core_rpc_server_error_codes.h" + +using namespace epee; namespace cryptonote { @@ -104,19 +105,17 @@ namespace cryptonote bool core_rpc_server::on_get_blocks(const COMMAND_RPC_GET_BLOCKS_FAST::request& req, COMMAND_RPC_GET_BLOCKS_FAST::response& res, connection_context& cntx) { CHECK_CORE_READY(); - std::list > > bs; + std::list > > bs; if(!m_core.find_blockchain_supplement(req.block_ids, bs, res.current_height, res.start_height, COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT)) { res.status = "Failed"; return false; } - BOOST_FOREACH(auto& b, bs) - { + for (auto& b : bs) { res.blocks.resize(res.blocks.size()+1); res.blocks.back().block = block_to_blob(b.first); - BOOST_FOREACH(auto& t, b.second) - { + for (auto& t : b.second) { res.blocks.back().txs.push_back(tx_to_blob(t)); } } @@ -124,6 +123,81 @@ namespace cryptonote res.status = CORE_RPC_STATUS_OK; return true; } + + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_query_blocks(const COMMAND_RPC_QUERY_BLOCKS::request& req, COMMAND_RPC_QUERY_BLOCKS::response& res, connection_context& cntx) + { + CHECK_CORE_READY(); + + typedef COMMAND_RPC_QUERY_BLOCKS::response_item ResponseItem; + + LockedBlockchainStorage lbs(m_core.get_blockchain_storage()); + + uint64_t currentHeight = lbs->get_current_blockchain_height(); + uint64_t startOffset = 0; + + if (!lbs->find_blockchain_supplement(req.block_ids, startOffset)) { + res.status = "Failed to find blockchain supplement"; + return false; + } + + uint64_t startFullOffset = 0; + + if (!lbs->getLowerBound(req.timestamp, startOffset, startFullOffset)) + startFullOffset = startOffset; + + res.full_offset = startFullOffset; + + if (startOffset != startFullOffset) { + std::list blockIds; + if (!lbs->getBlockIds(startOffset, std::min(uint64_t(BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT), startFullOffset - startOffset), blockIds)) { + res.status = "Failed to get block ids"; + return false; + } + + for (const auto& id : blockIds) { + res.items.push_back(ResponseItem()); + res.items.back().block_id = id; + } + } + + auto blocksLeft = std::min(BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT - res.items.size(), size_t(BLOCKS_SYNCHRONIZING_DEFAULT_COUNT)); + + if (blocksLeft) { + std::list blocks; + lbs->get_blocks(startFullOffset, blocksLeft, blocks); + + for (auto& b : blocks) { + + ResponseItem item; + + item.block_id = get_block_hash(b); + + if (b.timestamp >= req.timestamp) { + // query transactions + std::list txs; + std::list missedTxs; + lbs->get_transactions(b.txHashes, txs, missedTxs); + + // fill data + block_complete_entry& completeEntry = item; + completeEntry.block = block_to_blob(b); + for (auto& tx : txs) { + completeEntry.txs.push_back(tx_to_blob(tx)); + } + } + + res.items.push_back(std::move(item)); + } + } + + res.current_height = currentHeight; + res.start_height = startOffset; + + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_random_outs(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res, connection_context& cntx) { @@ -172,8 +246,7 @@ namespace cryptonote { CHECK_CORE_READY(); std::vector vh; - BOOST_FOREACH(const auto& tx_hex_str, req.txs_hashes) - { + for (const auto& tx_hex_str : req.txs_hashes) { blobdata b; if(!string_tools::parse_hexstr_to_binbuff(tx_hex_str, b)) { @@ -187,17 +260,15 @@ namespace cryptonote vh.push_back(*reinterpret_cast(b.data())); } std::list missed_txs; - std::list txs; + std::list txs; m_core.get_transactions(vh, txs, missed_txs); - BOOST_FOREACH(auto& tx, txs) - { + for (auto& tx : txs) { blobdata blob = t_serializable_object_to_blob(tx); res.txs_as_hex.push_back(string_tools::buff_to_hex_nodelimer(blob)); } - BOOST_FOREACH(const auto& miss_tx, missed_txs) - { + for (const auto& miss_tx : missed_txs) { res.missed_tx.push_back(string_tools::pod_to_hex(miss_tx)); } @@ -252,8 +323,8 @@ namespace cryptonote bool core_rpc_server::on_start_mining(const COMMAND_RPC_START_MINING::request& req, COMMAND_RPC_START_MINING::response& res, connection_context& cntx) { CHECK_CORE_READY(); - account_public_address adr; - if(!get_account_address_from_str(adr, req.miner_address)) + AccountPublicAddress adr; + if(!m_core.currency().parseAccountAddressString(req.miner_address, adr)) { res.status = "Failed, wrong address"; return true; @@ -346,16 +417,16 @@ namespace cryptonote return false; } - cryptonote::account_public_address acc = AUTO_VAL_INIT(acc); + cryptonote::AccountPublicAddress acc = AUTO_VAL_INIT(acc); - if(!req.wallet_address.size() || !cryptonote::get_account_address_from_str(acc, req.wallet_address)) + if(!req.wallet_address.size() || !m_core.currency().parseAccountAddressString(req.wallet_address, acc)) { error_resp.code = CORE_RPC_ERROR_CODE_WRONG_WALLET_ADDRESS; error_resp.message = "Failed to parse wallet address"; return false; } - block b = AUTO_VAL_INIT(b); + Block b = AUTO_VAL_INIT(b); cryptonote::blobdata blob_reserve; blob_reserve.resize(req.reserve_size, 0); if(!m_core.get_block_template(b, acc, res.difficulty, res.height, blob_reserve)) @@ -367,7 +438,7 @@ namespace cryptonote } blobdata block_blob = t_serializable_object_to_blob(b); - crypto::public_key tx_pub_key = cryptonote::get_tx_pub_key_from_extra(b.miner_tx); + crypto::public_key tx_pub_key = cryptonote::get_tx_pub_key_from_extra(b.minerTx); if(tx_pub_key == null_pkey) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; @@ -402,33 +473,37 @@ namespace cryptonote res.blocktemplate_blob = string_tools::buff_to_hex_nodelimer(block_blob); res.status = CORE_RPC_STATUS_OK; + return true; } //------------------------------------------------------------------------------------------------------------------------------ - 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) + bool core_rpc_server::on_get_currency_id(const COMMAND_RPC_GET_CURRENCY_ID::request& /*req*/, COMMAND_RPC_GET_CURRENCY_ID::response& res, epee::json_rpc::error& error_resp, connection_context& /*cntx*/) { + crypto::hash currencyId = m_core.currency().genesisBlockHash(); + blobdata blob = t_serializable_object_to_blob(currencyId); + res.currency_id_blob = string_tools::buff_to_hex_nodelimer(blob); + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + 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(); - if(req.size()!=1) - { + if (req.size() != 1) { error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; error_resp.message = "Wrong param"; return false; } + blobdata blockblob; - if(!string_tools::parse_hexstr_to_binbuff(req[0], blockblob)) - { + if (!string_tools::parse_hexstr_to_binbuff(req[0], blockblob)) { error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB; error_resp.message = "Wrong block blob"; return false; } - cryptonote::block_verification_context bvc = AUTO_VAL_INIT(bvc); - m_core.handle_incoming_block(blockblob, bvc); - if (bvc.m_added_to_main_chain){ - block b = AUTO_VAL_INIT(b); - parse_and_validate_block_from_blob(blockblob, b); - m_core.notify_new_block(b); - } else { + cryptonote::block_verification_context bvc = AUTO_VAL_INIT(bvc); + m_core.handle_incoming_block_blob(blockblob, bvc, true, true); + if (!bvc.m_added_to_main_chain) { error_resp.code = CORE_RPC_ERROR_CODE_BLOCK_NOT_ACCEPTED; error_resp.message = "Block not accepted"; return false; @@ -438,22 +513,22 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ - uint64_t core_rpc_server::get_block_reward(const block& blk) - { - uint64_t reward = 0; - BOOST_FOREACH(const tx_out& out, blk.miner_tx.vout) - { - reward += out.amount; + namespace { + uint64_t get_block_reward(const Block& blk) { + uint64_t reward = 0; + for (const TransactionOutput& out : blk.minerTx.vout) { + reward += out.amount; + } + return reward; } - return reward; } //------------------------------------------------------------------------------------------------------------------------------ - bool core_rpc_server::fill_block_header_responce(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_responce& responce) + bool core_rpc_server::fill_block_header_responce(const Block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_responce& responce) { - responce.major_version = blk.major_version; - responce.minor_version = blk.minor_version; + responce.major_version = blk.majorVersion; + responce.minor_version = blk.minorVersion; responce.timestamp = blk.timestamp; - responce.prev_hash = string_tools::pod_to_hex(blk.prev_id); + responce.prev_hash = string_tools::pod_to_hex(blk.prevId); responce.nonce = blk.nonce; responce.orphan_status = orphan_status; responce.height = height; @@ -481,7 +556,7 @@ namespace cryptonote error_resp.message = "Internal error: can't get last block hash."; return false; } - block last_block; + Block last_block; bool have_last_block = m_core.get_block_by_hash(last_block_hash, last_block); if (!have_last_block) { @@ -515,7 +590,7 @@ namespace cryptonote error_resp.message = "Failed to parse hex representation of block hash. Hex = " + req.hash + '.'; return false; } - block blk; + Block blk; bool have_block = m_core.get_block_by_hash(block_hash, blk); if (!have_block) { @@ -523,13 +598,13 @@ namespace cryptonote error_resp.message = "Internal error: can't get block by hash. Hash = " + req.hash + '.'; return false; } - if (blk.miner_tx.vin.front().type() != typeid(txin_gen)) + if (blk.minerTx.vin.front().type() != typeid(TransactionInputGenerate)) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Internal error: coinbase transaction in the block has the wrong type"; return false; } - uint64_t block_height = boost::get(blk.miner_tx.vin.front()).height; + uint64_t block_height = boost::get(blk.minerTx.vin.front()).height; bool responce_filled = fill_block_header_responce(blk, false, block_height, block_hash, res.block_header); if (!responce_filled) { @@ -555,7 +630,7 @@ namespace cryptonote return false; } crypto::hash block_hash = m_core.get_block_id_by_height(req.height); - block blk; + Block blk; bool have_block = m_core.get_block_by_hash(block_hash, blk); if (!have_block) { diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 135733d3..bdb9dbe7 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -47,7 +47,8 @@ namespace cryptonote BEGIN_URI_MAP2() MAP_URI_AUTO_JON2("/getheight", on_get_height, COMMAND_RPC_GET_HEIGHT) MAP_URI_AUTO_BIN2("/getblocks.bin", on_get_blocks, COMMAND_RPC_GET_BLOCKS_FAST) - MAP_URI_AUTO_BIN2("/get_o_indexes.bin", on_get_indexes, COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES) + MAP_URI_AUTO_BIN2("/queryblocks.bin", on_query_blocks, COMMAND_RPC_QUERY_BLOCKS) + MAP_URI_AUTO_BIN2("/get_o_indexes.bin", on_get_indexes, COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES) MAP_URI_AUTO_BIN2("/getrandom_outs.bin", on_get_random_outs, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS) MAP_URI_AUTO_JON2("/gettransactions", on_get_transactions, COMMAND_RPC_GET_TRANSACTIONS) MAP_URI_AUTO_JON2("/sendrawtransaction", on_send_raw_tx, COMMAND_RPC_SEND_RAW_TX) @@ -58,6 +59,7 @@ namespace cryptonote MAP_JON_RPC("getblockcount", on_getblockcount, COMMAND_RPC_GETBLOCKCOUNT) MAP_JON_RPC_WE("on_getblockhash", on_getblockhash, COMMAND_RPC_GETBLOCKHASH) MAP_JON_RPC_WE("getblocktemplate", on_getblocktemplate, COMMAND_RPC_GETBLOCKTEMPLATE) + MAP_JON_RPC_WE("getcurrencyid", on_get_currency_id, COMMAND_RPC_GET_CURRENCY_ID) MAP_JON_RPC_WE("submitblock", on_submitblock, COMMAND_RPC_SUBMITBLOCK) MAP_JON_RPC_WE("getlastblockheader", on_get_last_block_header, COMMAND_RPC_GET_LAST_BLOCK_HEADER) MAP_JON_RPC_WE("getblockheaderbyhash", on_get_block_header_by_hash, COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH) @@ -67,6 +69,7 @@ namespace cryptonote bool on_get_height(const COMMAND_RPC_GET_HEIGHT::request& req, COMMAND_RPC_GET_HEIGHT::response& res, connection_context& cntx); bool on_get_blocks(const COMMAND_RPC_GET_BLOCKS_FAST::request& req, COMMAND_RPC_GET_BLOCKS_FAST::response& res, connection_context& cntx); + bool on_query_blocks(const COMMAND_RPC_QUERY_BLOCKS::request& req, COMMAND_RPC_QUERY_BLOCKS::response& res, connection_context& cntx); bool on_get_transactions(const COMMAND_RPC_GET_TRANSACTIONS::request& req, COMMAND_RPC_GET_TRANSACTIONS::response& res, connection_context& cntx); bool on_get_indexes(const COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request& req, COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response& res, connection_context& cntx); bool on_send_raw_tx(const COMMAND_RPC_SEND_RAW_TX::request& req, COMMAND_RPC_SEND_RAW_TX::response& res, connection_context& cntx); @@ -79,6 +82,7 @@ namespace cryptonote bool on_getblockcount(const COMMAND_RPC_GETBLOCKCOUNT::request& req, COMMAND_RPC_GETBLOCKCOUNT::response& res, connection_context& cntx); bool on_getblockhash(const COMMAND_RPC_GETBLOCKHASH::request& req, COMMAND_RPC_GETBLOCKHASH::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); bool on_getblocktemplate(const COMMAND_RPC_GETBLOCKTEMPLATE::request& req, COMMAND_RPC_GETBLOCKTEMPLATE::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); + bool on_get_currency_id(const COMMAND_RPC_GET_CURRENCY_ID::request& req, COMMAND_RPC_GET_CURRENCY_ID::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); bool on_submitblock(const COMMAND_RPC_SUBMITBLOCK::request& req, COMMAND_RPC_SUBMITBLOCK::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); bool on_get_last_block_header(const COMMAND_RPC_GET_LAST_BLOCK_HEADER::request& req, COMMAND_RPC_GET_LAST_BLOCK_HEADER::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); bool on_get_block_header_by_hash(const COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::request& req, COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); @@ -88,8 +92,7 @@ namespace cryptonote bool check_core_ready(); //utils - uint64_t get_block_reward(const block& blk); - bool fill_block_header_responce(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_responce& responce); + bool fill_block_header_responce(const Block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_responce& responce); core& m_core; nodetool::node_server >& m_p2p; diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 1b9c5d9f..a9b8e912 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -124,46 +124,52 @@ namespace cryptonote }; }; //----------------------------------------------- - struct COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS + struct COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_request { - struct request - { - std::vector amounts; - uint64_t outs_count; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(amounts) - KV_SERIALIZE(outs_count) - END_KV_SERIALIZE_MAP() - }; + std::vector amounts; + uint64_t outs_count; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(amounts) + KV_SERIALIZE(outs_count) + END_KV_SERIALIZE_MAP() + }; #pragma pack (push, 1) - struct out_entry - { - uint64_t global_amount_index; - crypto::public_key out_key; - }; + struct COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_out_entry + { + uint64_t global_amount_index; + crypto::public_key out_key; + }; #pragma pack(pop) - struct outs_for_amount - { - uint64_t amount; - std::list outs; + struct COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_outs_for_amount + { + uint64_t amount; + std::list outs; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(amount) - KV_SERIALIZE_CONTAINER_POD_AS_BLOB(outs) - END_KV_SERIALIZE_MAP() - }; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(amount) + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(outs) + END_KV_SERIALIZE_MAP() + }; - struct response - { - std::vector outs; - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(outs) - KV_SERIALIZE(status) - END_KV_SERIALIZE_MAP() - }; + struct COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_response + { + std::vector outs; + std::string status; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(outs) + KV_SERIALIZE(status) + END_KV_SERIALIZE_MAP() + }; + + struct COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS + { + typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_request request; + typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_response response; + + typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_out_entry out_entry; + typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_outs_for_amount outs_for_amount; }; //----------------------------------------------- struct COMMAND_RPC_SEND_RAW_TX @@ -173,7 +179,7 @@ namespace cryptonote std::string tx_as_hex; request() {} - explicit request(const transaction &); + explicit request(const Transaction &); BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_as_hex) @@ -331,6 +337,24 @@ namespace cryptonote }; }; + struct COMMAND_RPC_GET_CURRENCY_ID + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string currency_id_blob; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(currency_id_blob) + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_SUBMITBLOCK { typedef std::vector request; @@ -443,4 +467,47 @@ namespace cryptonote }; + struct COMMAND_RPC_QUERY_BLOCKS + { + struct request + { + std::list block_ids; //*first 10 blocks id goes sequential, next goes in pow(2,n) offset, like 2, 4, 8, 16, 32, 64 and so on, and the last one is always genesis block */ + uint64_t timestamp; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(block_ids) + KV_SERIALIZE(timestamp) + END_KV_SERIALIZE_MAP() + }; + + struct response_item : public block_complete_entry + { + crypto::hash block_id; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_VAL_POD_AS_BLOB(block_id) + KV_SERIALIZE(block) + KV_SERIALIZE(txs) + END_KV_SERIALIZE_MAP() + + }; + + struct response + { + std::string status; + uint64_t start_height; + uint64_t current_height; + uint64_t full_offset; + + std::list items; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + KV_SERIALIZE(start_height) + KV_SERIALIZE(current_height) + KV_SERIALIZE(full_offset) + KV_SERIALIZE(items) + END_KV_SERIALIZE_MAP() + }; + }; } diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 785a630a..51f93450 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -19,15 +19,19 @@ #include #include #include + +// epee #include "include_base_utils.h" -#include "common/command_line.h" -#include "common/util.h" -#include "p2p/net_node.h" -#include "cryptonote_protocol/cryptonote_protocol_handler.h" -#include "simplewallet.h" -#include "cryptonote_core/cryptonote_format_utils.h" #include "storages/http_abstract_invoke.h" + +#include "common/command_line.h" +#include "common/SignalHandler.h" +#include "common/util.h" +#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_protocol/cryptonote_protocol_handler.h" +#include "p2p/net_node.h" #include "rpc/core_rpc_server_commands_defs.h" +#include "simplewallet.h" #include "wallet/wallet_rpc_server.h" #include "version.h" @@ -53,6 +57,7 @@ namespace const command_line::arg_descriptor arg_password = {"password", "Wallet password", "", true}; const command_line::arg_descriptor arg_daemon_port = {"daemon-port", "Use daemon instance at port instead of 8081", 0}; const command_line::arg_descriptor arg_log_level = {"set_log", "", 0, true}; + const command_line::arg_descriptor arg_testnet = {"testnet", "Used to deploy test nets. The daemon must be launched with --testnet flag", false}; const command_line::arg_descriptor< std::vector > arg_command = {"command", ""}; @@ -155,6 +160,123 @@ namespace { return message_writer(epee::log_space::console_color_red, true, "Error: ", LOG_LEVEL_0); } + + + template + class ArgumentReader { + public: + + ArgumentReader(IterT begin, IterT end) : + m_begin(begin), m_end(end), m_cur(begin) {} + + bool eof() const { + return m_cur == m_end; + } + + ValueT next() { + if (eof()) { + throw std::runtime_error("unexpected end of arguments"); + } + + return *m_cur++; + } + + private: + + IterT m_cur; + IterT m_begin; + IterT m_end; + }; + + struct TransferCommand { + const cryptonote::Currency& m_currency; + size_t fake_outs_count; + vector dsts; + std::vector extra; + uint64_t fee; + + TransferCommand(const cryptonote::Currency& currency) : + m_currency(currency), fake_outs_count(0), fee(currency.minimumFee()) { + } + + bool parseArguments(const std::vector &args) { + + ArgumentReader::const_iterator> ar(args.begin(), args.end()); + + try { + + auto mixin_str = ar.next(); + + if (!epee::string_tools::get_xtype_from_string(fake_outs_count, mixin_str)) { + fail_msg_writer() << "mixin_count should be non-negative integer, got " << mixin_str; + return false; + } + + while (!ar.eof()) { + + auto arg = ar.next(); + + if (arg.size() && arg[0] == '-') { + + const auto& value = ar.next(); + + if (arg == "-p") { + crypto::hash payment_id; + bool r = tools::wallet2::parse_payment_id(value, payment_id); + if (r) { + std::string extra_nonce; + set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); + r = add_extra_nonce_to_tx_extra(extra, extra_nonce); + } + + if (!r) { + fail_msg_writer() << "payment id has invalid format: \"" << value << "\", expected 64-character string"; + return false; + } + } else if (arg == "-f") { + bool ok = m_currency.parseAmount(value, fee); + if (!ok) { + fail_msg_writer() << "Fee value is invalid: " << value; + return false; + } + + if (fee < m_currency.minimumFee()) { + fail_msg_writer() << "Fee value is less than minimum: " << m_currency.minimumFee(); + return false; + } + } + } else { + cryptonote::tx_destination_entry de; + + if (!m_currency.parseAccountAddressString(arg, de.addr)) { + fail_msg_writer() << "wrong address: " << arg; + return false; + } + + auto value = ar.next(); + bool ok = m_currency.parseAmount(value, de.amount); + if (!ok || 0 == de.amount) { + fail_msg_writer() << "amount is wrong: " << arg << ' ' << value << + ", expected number from 0 to " << m_currency.formatAmount(std::numeric_limits::max()); + return false; + } + + dsts.push_back(de); + } + } + + if (dsts.empty()) { + fail_msg_writer() << "At least one destination address is required"; + return false; + } + } catch (const std::exception& e) { + fail_msg_writer() << e.what(); + return false; + } + + return true; + } + }; } @@ -175,8 +297,9 @@ bool simple_wallet::help(const std::vector &args/* = std::vector] - Start mining in daemon"); @@ -186,7 +309,10 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("incoming_transfers", boost::bind(&simple_wallet::show_incoming_transfers, this, _1), "incoming_transfers [available|unavailable] - Show incoming transfers - all of them or filter them by availability"); m_cmd_binder.set_handler("payments", boost::bind(&simple_wallet::show_payments, this, _1), "payments [ ... ] - Show payments , ... "); m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), "Show blockchain height"); - m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1), "transfer [ ... ] [payment_id] - Transfer ,... to ,... , respectively. is the number of transactions yours is indistinguishable from (from 0 to maximum available)"); + m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1), + "transfer [ ... ] [-p payment_id] [-f fee]" + " - Transfer ,... to ,... , respectively. " + " is the number of transactions yours is indistinguishable from (from 0 to maximum available)"); m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), "set_log - Change current log detalization level, is a number 0-4"); m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), "Show current wallet public address"); m_cmd_binder.set_handler("save", boost::bind(&simple_wallet::save, this, _1), "Save wallet synchronized data"); @@ -341,12 +467,14 @@ bool simple_wallet::new_wallet(const string &wallet_file, const std::string& pas { m_wallet_file = wallet_file; - m_wallet.reset(new tools::wallet2()); + m_wallet.reset(new tools::wallet2(m_currency)); m_wallet->callback(this); try { m_wallet->generate(wallet_file, password); - message_writer(epee::log_space::console_color_white, true) << "Generated new wallet: " << m_wallet->get_account().get_public_address_str() << std::endl << "view key: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_view_secret_key); + message_writer(epee::log_space::console_color_white, true) << + "Generated new wallet: " << m_currency.accountAddressAsString(m_wallet->get_account()) << std::endl << + "view key: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_view_secret_key); } catch (const std::exception& e) { @@ -371,13 +499,14 @@ bool simple_wallet::new_wallet(const string &wallet_file, const std::string& pas bool simple_wallet::open_wallet(const string &wallet_file, const std::string& password) { m_wallet_file = wallet_file; - m_wallet.reset(new tools::wallet2()); + m_wallet.reset(new tools::wallet2(m_currency)); m_wallet->callback(this); try { m_wallet->load(m_wallet_file, password); - message_writer(epee::log_space::console_color_white, true) << "Opened wallet: " << m_wallet->get_account().get_public_address_str(); + message_writer(epee::log_space::console_color_white, true) << "Opened wallet: " << + m_currency.accountAddressAsString(m_wallet->get_account()); } catch (const std::exception& e) { @@ -438,7 +567,7 @@ bool simple_wallet::start_mining(const std::vector& args) return true; COMMAND_RPC_START_MINING::request req; - req.miner_address = m_wallet->get_account().get_public_address_str(); + req.miner_address = m_currency.accountAddressAsString(m_wallet->get_account()); bool ok = true; size_t max_mining_threads_count = (std::max)(std::thread::hardware_concurrency(), static_cast(2)); @@ -491,30 +620,30 @@ bool simple_wallet::stop_mining(const std::vector& args) return true; } //---------------------------------------------------------------------------------------------------- -void simple_wallet::on_new_block(uint64_t height, const cryptonote::block& block) +void simple_wallet::on_new_block(uint64_t height) { m_refresh_progress_reporter.update(height, false); } //---------------------------------------------------------------------------------------------------- -void simple_wallet::on_money_received(uint64_t height, const cryptonote::transaction& tx, size_t out_index) +void simple_wallet::on_money_received(uint64_t height, const cryptonote::Transaction& tx, size_t out_index) { message_writer(epee::log_space::console_color_green, false) << "Height " << height << ", transaction " << get_transaction_hash(tx) << - ", received " << print_money(tx.vout[out_index].amount); + ", received " << m_currency.formatAmount(tx.vout[out_index].amount); m_refresh_progress_reporter.update(height, true); } //---------------------------------------------------------------------------------------------------- -void simple_wallet::on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, size_t out_index, const cryptonote::transaction& spend_tx) +void simple_wallet::on_money_spent(uint64_t height, const cryptonote::Transaction& in_tx, size_t out_index, const cryptonote::Transaction& spend_tx) { message_writer(epee::log_space::console_color_magenta, false) << "Height " << height << ", transaction " << get_transaction_hash(spend_tx) << - ", spent " << print_money(in_tx.vout[out_index].amount); + ", spent " << m_currency.formatAmount(in_tx.vout[out_index].amount); m_refresh_progress_reporter.update(height, true); } //---------------------------------------------------------------------------------------------------- -void simple_wallet::on_skip_transaction(uint64_t height, const cryptonote::transaction& tx) +void simple_wallet::on_skip_transaction(uint64_t height, const cryptonote::Transaction& tx) { message_writer(epee::log_space::console_color_red, true) << "Height " << height << @@ -585,7 +714,8 @@ bool simple_wallet::refresh(const std::vector& args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::show_balance(const std::vector& args/* = std::vector()*/) { - success_msg_writer() << "balance: " << print_money(m_wallet->balance()) << ", unlocked balance: " << print_money(m_wallet->unlocked_balance()); + success_msg_writer() << "balance: " << m_currency.formatAmount(m_wallet->balance()) << + ", unlocked balance: " << m_currency.formatAmount(m_wallet->unlocked_balance()); return true; } //---------------------------------------------------------------------------------------------------- @@ -621,7 +751,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector& args transfers_found = true; } message_writer(td.m_spent ? epee::log_space::console_color_magenta : epee::log_space::console_color_green, false) << - std::setw(21) << print_money(td.amount()) << '\t' << + std::setw(21) << m_currency.formatAmount(td.amount()) << '\t' << std::setw(3) << (td.m_spent ? 'T' : 'F') << " \t" << std::setw(12) << td.m_global_output_index << '\t' << get_transaction_hash(td.m_tx); @@ -683,7 +813,7 @@ bool simple_wallet::show_payments(const std::vector &args) payment_id << '\t' << pd.m_tx_hash << '\t' << std::setw(8) << pd.m_block_height << '\t' << - std::setw(21) << print_money(pd.m_amount) << '\t' << + std::setw(21) << m_currency.formatAmount(pd.m_amount) << '\t' << pd.m_unlock_time; } } @@ -719,73 +849,20 @@ bool simple_wallet::show_blockchain_height(const std::vector& args) return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::transfer(const std::vector &args_) +bool simple_wallet::transfer(const std::vector &args) { if (!try_connect_to_daemon()) return true; - std::vector local_args = args_; - if(local_args.size() < 3) - { - fail_msg_writer() << "wrong number of arguments, expected at least 3, got " << local_args.size(); - return true; - } - - size_t fake_outs_count; - if(!epee::string_tools::get_xtype_from_string(fake_outs_count, local_args[0])) - { - fail_msg_writer() << "mixin_count should be non-negative integer, got " << local_args[0]; - return true; - } - local_args.erase(local_args.begin()); - - std::vector extra; - if (1 == local_args.size() % 2) - { - std::string payment_id_str = local_args.back(); - local_args.pop_back(); - - crypto::hash payment_id; - bool r = tools::wallet2::parse_payment_id(payment_id_str, payment_id); - if(r) - { - std::string extra_nonce; - set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); - r = add_extra_nonce_to_tx_extra(extra, extra_nonce); - } - - if(!r) - { - fail_msg_writer() << "payment id has invalid format: \"" << payment_id_str << "\", expected 64-character string"; - return true; - } - } - - vector dsts; - for (size_t i = 0; i < local_args.size(); i += 2) - { - cryptonote::tx_destination_entry de; - if(!get_account_address_from_str(de.addr, local_args[i])) - { - fail_msg_writer() << "wrong address: " << local_args[i]; - return true; - } - - bool ok = cryptonote::parse_amount(de.amount, local_args[i + 1]); - if(!ok || 0 == de.amount) - { - fail_msg_writer() << "amount is wrong: " << local_args[i] << ' ' << local_args[i + 1] << - ", expected number from 0 to " << print_money(std::numeric_limits::max()); - return true; - } - - dsts.push_back(de); - } - try { - cryptonote::transaction tx; - m_wallet->transfer(dsts, fake_outs_count, 0, MINIMUM_FEE, extra, tx); + TransferCommand cmd(m_currency); + + if (!cmd.parseArguments(args)) + return true; + + cryptonote::Transaction tx; + m_wallet->transfer(cmd.dsts, cmd.fake_outs_count, 0, cmd.fee, cmd.extra, tx); success_msg_writer(true) << "Money successfully sent, transaction " << get_transaction_hash(tx); } catch (const tools::error::daemon_busy&) @@ -807,9 +884,9 @@ bool simple_wallet::transfer(const std::vector &args_) } catch (const tools::error::not_enough_money& e) { - fail_msg_writer() << "not enough money to transfer, available only " << print_money(e.available()) << - ", transaction amount " << print_money(e.tx_amount() + e.fee()) << " = " << print_money(e.tx_amount()) << - " + " << print_money(e.fee()) << " (fee)"; + fail_msg_writer() << "not enough money to transfer, available only " << m_currency.formatAmount(e.available()) << + ", transaction amount " << m_currency.formatAmount(e.tx_amount() + e.fee()) << " = " << + m_currency.formatAmount(e.tx_amount()) << " + " << m_currency.formatAmount(e.fee()) << " (fee)"; } catch (const tools::error::not_enough_outs_to_mix& e) { @@ -817,7 +894,8 @@ bool simple_wallet::transfer(const std::vector &args_) writer << "not enough outputs for specified mixin_count = " << e.mixin_count() << ":"; for (const cryptonote::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& outs_for_amount : e.scanty_outs()) { - writer << "\noutput amount = " << print_money(outs_for_amount.amount) << ", fount outputs to mix = " << outs_for_amount.outs.size(); + writer << "\noutput amount = " << m_currency.formatAmount(outs_for_amount.amount) << + ", fount outputs to mix = " << outs_for_amount.outs.size(); } } catch (const tools::error::tx_not_constructed&) @@ -834,7 +912,7 @@ bool simple_wallet::transfer(const std::vector &args_) } catch (const tools::error::tx_too_big& e) { - cryptonote::transaction tx = e.tx(); + cryptonote::Transaction tx = e.tx(); fail_msg_writer() << "transaction " << get_transaction_hash(e.tx()) << " is too big. Transaction size: " << get_object_blobsize(e.tx()) << " bytes, transaction size limit: " << e.tx_size_limit() << " bytes"; } @@ -868,7 +946,7 @@ bool simple_wallet::transfer(const std::vector &args_) //---------------------------------------------------------------------------------------------------- bool simple_wallet::run() { - std::string addr_start = m_wallet->get_account().get_public_address_str().substr(0, 6); + std::string addr_start = m_currency.accountAddressAsString(m_wallet->get_account()).substr(0, 6); return m_cmd_binder.run_handling("[wallet " + addr_start + "]: ", ""); } //---------------------------------------------------------------------------------------------------- @@ -880,7 +958,7 @@ void simple_wallet::stop() //---------------------------------------------------------------------------------------------------- bool simple_wallet::print_address(const std::vector &args/* = std::vector()*/) { - success_msg_writer() << m_wallet->get_account().get_public_address_str(); + success_msg_writer() << m_currency.accountAddressAsString(m_wallet->get_account()); return true; } //---------------------------------------------------------------------------------------------------- @@ -912,6 +990,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_daemon_port); command_line::add_arg(desc_params, arg_command); command_line::add_arg(desc_params, arg_log_level); + command_line::add_arg(desc_params, arg_testnet); tools::wallet_rpc_server::init_options(desc_params); po::positional_options_description positional_options; @@ -919,7 +998,8 @@ int main(int argc, char* argv[]) po::options_description desc_all; desc_all.add(desc_general).add(desc_params); - cryptonote::simple_wallet w; + cryptonote::Currency tmp_currency = cryptonote::CurrencyBuilder().currency(); + cryptonote::simple_wallet tmp_wallet(tmp_currency); po::variables_map vm; bool r = command_line::handle_error_helper(desc_all, [&]() { @@ -927,9 +1007,9 @@ int main(int argc, char* argv[]) if (command_line::get_arg(vm, command_line::arg_help)) { - success_msg_writer() << "bytecoin wallet v" << PROJECT_VERSION_LONG; + success_msg_writer() << CRYPTONOTE_NAME << " wallet v" << PROJECT_VERSION_LONG; success_msg_writer() << "Usage: simplewallet [--wallet-file=|--generate-new-wallet=] [--daemon-address=:] []"; - success_msg_writer() << desc_all << '\n' << w.get_commands_str(); + success_msg_writer() << desc_all << '\n' << tmp_wallet.get_commands_str(); return false; } else if (command_line::get_arg(vm, command_line::arg_version)) @@ -961,6 +1041,10 @@ int main(int argc, char* argv[]) log_space::get_set_log_detalisation_level(true, command_line::get_arg(vm, arg_log_level)); } + cryptonote::CurrencyBuilder currencyBuilder; + currencyBuilder.testnet(command_line::get_arg(vm, arg_testnet)); + cryptonote::Currency currency = currencyBuilder.currency(); + if(command_line::has_arg(vm, tools::wallet_rpc_server::arg_rpc_bind_port)) { log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL, LOG_LEVEL_2); @@ -993,7 +1077,7 @@ int main(int argc, char* argv[]) if (daemon_address.empty()) daemon_address = std::string("http://") + daemon_host + ":" + std::to_string(daemon_port); - tools::wallet2 wal; + tools::wallet2 wal(currency); try { LOG_PRINT_L0("Loading wallet..."); @@ -1011,7 +1095,7 @@ int main(int argc, char* argv[]) bool r = wrpc.init(vm); CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize wallet rpc server"); - tools::signal_handler::install([&wrpc, &wal] { + tools::SignalHandler::install([&wrpc, &wal] { wrpc.send_stop_signal(); wal.store(); }); @@ -1032,19 +1116,20 @@ int main(int argc, char* argv[]) }else { //runs wallet with console interface - r = w.init(vm); + cryptonote::simple_wallet wal(currency); + r = wal.init(vm); CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize wallet"); std::vector command = command_line::get_arg(vm, arg_command); if (!command.empty()) - w.process_command(command); + wal.process_command(command); - tools::signal_handler::install([&w] { - w.stop(); + tools::SignalHandler::install([&wal] { + wal.stop(); }); - w.run(); + wal.run(); - w.deinit(); + wal.deinit(); } return 1; //CATCH_ENTRY_L0("main", 1); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index f462cefe..529b9d0a 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -21,8 +21,8 @@ #include -#include "cryptonote_core/account.h" #include "cryptonote_core/cryptonote_basic_impl.h" +#include "cryptonote_core/Currency.h" #include "wallet/wallet2.h" #include "console_handler.h" #include "password_container.h" @@ -38,7 +38,7 @@ namespace cryptonote public: typedef std::vector command_type; - simple_wallet(); + simple_wallet(const cryptonote::Currency& currency); bool init(const boost::program_options::variables_map& vm); bool deinit(); bool run(); @@ -47,6 +47,9 @@ namespace cryptonote //wallet *create_wallet(); bool process_command(const std::vector &args); std::string get_commands_str(); + + const cryptonote::Currency& currency() const { return m_currency; } + private: void handle_command_line(const boost::program_options::variables_map& vm); @@ -74,10 +77,10 @@ namespace cryptonote bool ask_wallet_create_if_needed(); //----------------- i_wallet2_callback --------------------- - virtual void on_new_block(uint64_t height, const cryptonote::block& block); - virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, size_t out_index); - virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, size_t out_index, const cryptonote::transaction& spend_tx); - virtual void on_skip_transaction(uint64_t height, const cryptonote::transaction& tx); + virtual void on_new_block(uint64_t height); + virtual void on_money_received(uint64_t height, const cryptonote::Transaction& tx, size_t out_index); + virtual void on_money_spent(uint64_t height, const cryptonote::Transaction& in_tx, size_t out_index, const cryptonote::Transaction& spend_tx); + virtual void on_skip_transaction(uint64_t height, const cryptonote::Transaction& tx); //---------------------------------------------------------- friend class refresh_progress_reporter_t; @@ -96,8 +99,8 @@ namespace cryptonote void update(uint64_t height, bool force = false) { auto current_time = std::chrono::system_clock::now(); - if (std::chrono::seconds(DIFFICULTY_TARGET / 2) < current_time - m_blockchain_height_update_time || m_blockchain_height <= height) - { + if (std::chrono::seconds(m_simple_wallet.currency().difficultyTarget() / 2) < current_time - m_blockchain_height_update_time || + m_blockchain_height <= height) { update_blockchain_height(); m_blockchain_height = (std::max)(m_blockchain_height, height); } @@ -143,6 +146,7 @@ namespace cryptonote epee::console_handlers_binder m_cmd_binder; + const cryptonote::Currency& m_currency; std::unique_ptr m_wallet; epee::net_utils::http::http_simple_client m_http_client; refresh_progress_reporter_t m_refresh_progress_reporter; diff --git a/src/version.h.in b/src/version.h.in index 62316da3..073f9e17 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -1,4 +1,4 @@ #define BUILD_COMMIT_ID "@VERSION@" -#define PROJECT_VERSION "0.8.11" -#define PROJECT_VERSION_BUILD_NO "65" +#define PROJECT_VERSION "1.0.0" +#define PROJECT_VERSION_BUILD_NO "312" #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO "(" BUILD_COMMIT_ID ")" diff --git a/src/wallet/Wallet.cpp b/src/wallet/Wallet.cpp index 18e3dd6f..ae2c8e8f 100644 --- a/src/wallet/Wallet.cpp +++ b/src/wallet/Wallet.cpp @@ -36,6 +36,7 @@ #include "WalletSerialization.h" #include #include +#include namespace { @@ -73,15 +74,34 @@ void runAtomic(std::mutex& mutex, F f) { namespace CryptoNote { -Wallet::Wallet(INode& node) : +void Wallet::WalletNodeObserver::postponeRefresh() { + std::unique_lock lock(postponeMutex); + postponed = true; +} + +void Wallet::WalletNodeObserver::saveCompleted(std::error_code result) { + bool startRefresh = false; + { + std::unique_lock lock(postponeMutex); + startRefresh = postponed; + postponed = false; + } + + if (startRefresh) { + m_wallet->startRefresh(); + } +} + +Wallet::Wallet(const cryptonote::Currency& currency, INode& node) : m_state(NOT_INITIALIZED), + m_currency(currency), m_node(node), m_isSynchronizing(false), m_isStopping(false), - m_transferDetails(m_blockchain), + m_transferDetails(currency, m_blockchain), m_transactionsCache(m_sendingTxsStates), m_synchronizer(m_account, m_node, m_blockchain, m_transferDetails, m_unconfirmedTransactions, m_transactionsCache), - m_sender(m_transactionsCache, m_sendingTxsStates, m_transferDetails, m_unconfirmedTransactions) { + m_sender(currency, m_transactionsCache, m_sendingTxsStates, m_transferDetails, m_unconfirmedTransactions) { m_autoRefresher.reset(new WalletNodeObserver(this)); } @@ -102,6 +122,7 @@ void Wallet::initAndGenerate(const std::string& password) { } m_node.addObserver(m_autoRefresher.get()); + addObserver(m_autoRefresher.get()); m_account.generate(); m_password = password; @@ -118,9 +139,7 @@ void Wallet::initAndGenerate(const std::string& password) { } void Wallet::storeGenesisBlock() { - cryptonote::block b; - cryptonote::generate_genesis_block(b); - m_blockchain.push_back(get_block_hash(b)); + m_blockchain.push_back(m_currency.genesisBlockHash()); } void Wallet::initAndLoad(std::istream& source, const std::string& password) { @@ -131,6 +150,7 @@ void Wallet::initAndLoad(std::istream& source, const std::string& password) { } m_node.addObserver(m_autoRefresher.get()); + addObserver(m_autoRefresher.get()); m_password = password; m_state = LOADING; @@ -167,8 +187,8 @@ void Wallet::doLoad(std::istream& source) { dataArchive >> m_account; - throwIfKeysMissmatch(m_account.get_keys().m_view_secret_key, m_account.get_keys().m_account_address.m_view_public_key); - throwIfKeysMissmatch(m_account.get_keys().m_spend_secret_key, m_account.get_keys().m_account_address.m_spend_public_key); + throwIfKeysMissmatch(m_account.get_keys().m_view_secret_key, m_account.get_keys().m_account_address.m_viewPublicKey); + throwIfKeysMissmatch(m_account.get_keys().m_spend_secret_key, m_account.get_keys().m_account_address.m_spendPublicKey); dataArchive >> m_blockchain; @@ -228,6 +248,7 @@ void Wallet::shutdown() { m_asyncContextCounter.waitAsyncContextsFinish(); m_node.removeObserver(m_autoRefresher.get()); + removeObserver(m_autoRefresher.get()); } void Wallet::save(std::ostream& destination, bool saveDetailed, bool saveCache) { @@ -328,7 +349,7 @@ std::string Wallet::getAddress() { std::unique_lock lock(m_cacheMutex); throwIfNotInitialised(); - return m_account.get_public_address_str(); + return m_currency.accountAddressAsString(m_account); } uint64_t Wallet::actualBalance() { @@ -374,7 +395,7 @@ TransactionId Wallet::findTransactionByTransferId(TransferId transferId) { return m_transactionsCache.findTransactionByTransferId(transferId); } -bool Wallet::getTransaction(TransactionId transactionId, Transaction& transaction) { +bool Wallet::getTransaction(TransactionId transactionId, TransactionInfo& transaction) { std::unique_lock lock(m_cacheMutex); throwIfNotInitialised(); @@ -476,6 +497,11 @@ void Wallet::refresh() { { std::unique_lock lock(m_cacheMutex); + if (m_state == SAVING) { + m_autoRefresher->postponeRefresh(); + return; + } + if (m_state != INITIALIZED) { return; } diff --git a/src/wallet/Wallet.h b/src/wallet/Wallet.h index e7a25b6d..a3639950 100644 --- a/src/wallet/Wallet.h +++ b/src/wallet/Wallet.h @@ -29,9 +29,9 @@ #include "WalletAsyncContextCounter.h" #include "WalletTxSendingState.h" #include "common/ObserverManager.h" -#include "cryptonote_core/account.h" #include "cryptonote_core/tx_extra.h" #include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_core/Currency.h" #include "WalletTransferDetails.h" #include "WalletUserTransactionsCache.h" #include "WalletUnconfirmedTransactions.h" @@ -43,7 +43,7 @@ namespace CryptoNote { class Wallet : public IWallet { public: - Wallet(INode& node); + Wallet(const cryptonote::Currency& currency, INode& node); ~Wallet() {}; virtual void addObserver(IWalletObserver* observer); @@ -67,7 +67,7 @@ public: virtual TransactionId findTransactionByTransferId(TransferId transferId); - virtual bool getTransaction(TransactionId transactionId, Transaction& transaction); + virtual bool getTransaction(TransactionId transactionId, TransactionInfo& transaction); virtual bool getTransfer(TransferId transferId, Transfer& transfer); virtual TransactionId sendTransaction(const Transfer& transfer, uint64_t fee, const std::string& extra = "", uint64_t mixIn = 0, uint64_t unlockTimestamp = 0); @@ -105,6 +105,7 @@ private: std::mutex m_cacheMutex; cryptonote::account_base m_account; std::string m_password; + const cryptonote::Currency& m_currency; INode& m_node; bool m_isSynchronizing; bool m_isStopping; @@ -119,12 +120,17 @@ private: WalletTxSendingState m_sendingTxsStates; WalletUserTransactionsCache m_transactionsCache; - struct WalletNodeObserver: public INodeObserver + struct WalletNodeObserver: public INodeObserver, public IWalletObserver { - WalletNodeObserver(Wallet* wallet) : m_wallet(wallet) {} + WalletNodeObserver(Wallet* wallet) : m_wallet(wallet), postponed(false) {} virtual void lastKnownBlockHeightUpdated(uint64_t height) { m_wallet->startRefresh(); } + virtual void saveCompleted(std::error_code result); + void postponeRefresh(); Wallet* m_wallet; + + std::mutex postponeMutex; + bool postponed; }; std::unique_ptr m_autoRefresher; diff --git a/src/wallet/WalletRequest.h b/src/wallet/WalletRequest.h index 1b59c861..45f89cca 100644 --- a/src/wallet/WalletRequest.h +++ b/src/wallet/WalletRequest.h @@ -17,6 +17,10 @@ #pragma once +#include +#include +#include + #include "INode.h" #include "WalletSynchronizationContext.h" @@ -92,7 +96,7 @@ private: class WalletRelayTransactionRequest: public WalletRequest { public: - WalletRelayTransactionRequest(const cryptonote::transaction& tx, Callback cb) : m_tx(tx), m_cb(cb) {}; + WalletRelayTransactionRequest(const cryptonote::Transaction& tx, Callback cb) : m_tx(tx), m_cb(cb) {}; virtual ~WalletRelayTransactionRequest() {}; virtual void perform(INode& node, std::function cb) @@ -101,7 +105,7 @@ public: } private: - cryptonote::transaction m_tx; + cryptonote::Transaction m_tx; Callback m_cb; }; diff --git a/src/wallet/WalletSendTransactionContext.h b/src/wallet/WalletSendTransactionContext.h index 6e6c363d..c27815d0 100644 --- a/src/wallet/WalletSendTransactionContext.h +++ b/src/wallet/WalletSendTransactionContext.h @@ -29,9 +29,9 @@ struct TxDustPolicy { uint64_t dustThreshold; bool addToFee; - cryptonote::account_public_address addrForDust; + cryptonote::AccountPublicAddress addrForDust; - TxDustPolicy(uint64_t a_dust_threshold = 0, bool an_add_to_fee = true, cryptonote::account_public_address an_addr_for_dust = cryptonote::account_public_address()) + TxDustPolicy(uint64_t a_dust_threshold = 0, bool an_add_to_fee = true, cryptonote::AccountPublicAddress an_addr_for_dust = cryptonote::AccountPublicAddress()) : dustThreshold(a_dust_threshold) , addToFee(an_add_to_fee) , addrForDust(an_addr_for_dust) diff --git a/src/wallet/WalletSerialization.h b/src/wallet/WalletSerialization.h index 378b6d2f..2126575b 100644 --- a/src/wallet/WalletSerialization.h +++ b/src/wallet/WalletSerialization.h @@ -23,6 +23,7 @@ #include #include +#include "cryptonote_core/AccountKVSerialization.h" #include "cryptonote_core/cryptonote_boost_serialization.h" #include "common/unordered_containers_boost_serialization.h" #include "storages/portable_storage_template_helper.h" @@ -37,19 +38,21 @@ inline void load(Archive & ar, cryptonote::account_base& account, const unsigned { std::string data; ar >> data; - epee::serialization::load_t_from_binary(account, data); + cryptonote::AccountBaseSerializer accountSerializer(account); + epee::serialization::load_t_from_binary(accountSerializer, data); } template inline void save(Archive & ar, const cryptonote::account_base& account, const unsigned int version) { std::string data; - epee::serialization::store_t_to_binary(account, data); + cryptonote::AccountBaseSerializer accountSerializer(account); + epee::serialization::store_t_to_binary(accountSerializer, data); ar << data; } template -inline void serialize(Archive & ar, CryptoNote::Transaction& tx, const unsigned int version) +inline void serialize(Archive & ar, CryptoNote::TransactionInfo& tx, const unsigned int version) { ar & tx.firstTransferId; ar & tx.transferCount; diff --git a/src/wallet/WalletSynchronizationContext.h b/src/wallet/WalletSynchronizationContext.h index 3682dcd3..5e42da6b 100644 --- a/src/wallet/WalletSynchronizationContext.h +++ b/src/wallet/WalletSynchronizationContext.h @@ -32,7 +32,7 @@ struct TransactionContextInfo { std::vector requestedOuts; std::vector globalIndices; - cryptonote::transaction transaction; + cryptonote::Transaction transaction; crypto::public_key transactionPubKey; }; diff --git a/src/wallet/WalletSynchronizer.cpp b/src/wallet/WalletSynchronizer.cpp index d85cab88..b96f8314 100644 --- a/src/wallet/WalletSynchronizer.cpp +++ b/src/wallet/WalletSynchronizer.cpp @@ -19,6 +19,8 @@ #include +#include "cryptonote_core/account.h" + #include "WalletErrors.h" #include "WalletUtils.h" @@ -29,7 +31,7 @@ void throwIf(bool expr, cryptonote::error::WalletErrorCodes ec) { throw std::system_error(make_error_code(ec)); } -bool getTxPubKey(const cryptonote::transaction& tx, crypto::public_key& key) { +bool getTxPubKey(const cryptonote::Transaction& tx, crypto::public_key& key) { std::vector extraFields; cryptonote::parse_tx_extra(tx.extra, extraFields); @@ -43,33 +45,33 @@ bool getTxPubKey(const cryptonote::transaction& tx, crypto::public_key& key) { return true; } -void findMyOuts(const cryptonote::account_keys& acc, const cryptonote::transaction& tx, const crypto::public_key& txPubKey, std::vector& outs, uint64_t& moneyTransfered) { +void findMyOuts(const cryptonote::account_keys& acc, const cryptonote::Transaction& tx, const crypto::public_key& txPubKey, std::vector& outs, uint64_t& moneyTransfered) { bool r = cryptonote::lookup_acc_outs(acc, tx, txPubKey, outs, moneyTransfered); throwIf(!r, cryptonote::error::INTERNAL_WALLET_ERROR); } -uint64_t countOverallTxOutputs(const cryptonote::transaction& tx) { +uint64_t countOverallTxOutputs(const cryptonote::Transaction& tx) { uint64_t amount = 0; - for (const cryptonote::tx_out& o: tx.vout) { + for (const cryptonote::TransactionOutput& o: tx.vout) { amount += o.amount; } return amount; } -uint64_t countOverallTxInputs(const cryptonote::transaction& tx) { +uint64_t countOverallTxInputs(const cryptonote::Transaction& tx) { uint64_t amount = 0; for (auto& in: tx.vin) { - if(in.type() != typeid(cryptonote::txin_to_key)) + if(in.type() != typeid(cryptonote::TransactionInputToKey)) continue; - amount += boost::get(in).amount; + amount += boost::get(in).amount; } return amount; } -void fillTransactionHash(const cryptonote::transaction& tx, CryptoNote::TransactionHash& hash) { +void fillTransactionHash(const cryptonote::Transaction& tx, CryptoNote::TransactionHash& hash) { crypto::hash h = cryptonote::get_transaction_hash(tx); memcpy(hash.data(), reinterpret_cast(&h), hash.size()); } @@ -213,6 +215,7 @@ bool WalletSynchronizer::processNewBlocks(ProcessParameters& parameters) { fillRequest = true; ++context->progress.blockIdx; + context->progress.minersTxProcessed = false; ++currentIndex; } } @@ -225,7 +228,7 @@ bool WalletSynchronizer::processNewBlocks(ProcessParameters& parameters) { } WalletSynchronizer::NextBlockAction WalletSynchronizer::handleNewBlockchainEntry(ProcessParameters& parameters, cryptonote::block_complete_entry& blockEntry, uint64_t height) { - cryptonote::block b; + cryptonote::Block b; bool r = cryptonote::parse_and_validate_block_from_blob(blockEntry.block, b); throwIf(!r, cryptonote::error::INTERNAL_WALLET_ERROR); @@ -260,11 +263,11 @@ WalletSynchronizer::NextBlockAction WalletSynchronizer::handleNewBlockchainEntry return SKIP; } -bool WalletSynchronizer::processNewBlockchainEntry(ProcessParameters& parameters, cryptonote::block_complete_entry& blockEntry, const cryptonote::block& b, crypto::hash& blockId, uint64_t height) { +bool WalletSynchronizer::processNewBlockchainEntry(ProcessParameters& parameters, cryptonote::block_complete_entry& blockEntry, const cryptonote::Block& b, crypto::hash& blockId, uint64_t height) { throwIf(height != m_blockchain.size(), cryptonote::error::INTERNAL_WALLET_ERROR); if(b.timestamp + 60*60*24 > m_account.get_createtime()) { - if (!processMinersTx(parameters, b.miner_tx, height, b.timestamp)) + if (!processMinersTx(parameters, b.minerTx, height, b.timestamp)) return false; auto txIt = blockEntry.txs.begin(); @@ -272,7 +275,7 @@ bool WalletSynchronizer::processNewBlockchainEntry(ProcessParameters& parameters for (; txIt != blockEntry.txs.end(); ++txIt) { auto& txblob = *txIt; - cryptonote::transaction tx; + cryptonote::Transaction tx; bool r = parse_and_validate_tx_from_blob(txblob, tx); throwIf(!r, cryptonote::error::INTERNAL_WALLET_ERROR); @@ -290,7 +293,7 @@ bool WalletSynchronizer::processNewBlockchainEntry(ProcessParameters& parameters return true; } -bool WalletSynchronizer::processMinersTx(ProcessParameters& parameters, const cryptonote::transaction& tx, uint64_t height, uint64_t timestamp) { +bool WalletSynchronizer::processMinersTx(ProcessParameters& parameters, const cryptonote::Transaction& tx, uint64_t height, uint64_t timestamp) { bool r = true; if (!parameters.context->progress.minersTxProcessed) { @@ -301,7 +304,7 @@ bool WalletSynchronizer::processMinersTx(ProcessParameters& parameters, const cr return r; } -bool WalletSynchronizer::processNewTransaction(ProcessParameters& parameters, const cryptonote::transaction& tx, uint64_t height, bool isCoinbase, uint64_t timestamp) { +bool WalletSynchronizer::processNewTransaction(ProcessParameters& parameters, const cryptonote::Transaction& tx, uint64_t height, bool isCoinbase, uint64_t timestamp) { bool res = true; processUnconfirmed(parameters, tx, height, timestamp); @@ -329,18 +332,18 @@ bool WalletSynchronizer::processNewTransaction(ProcessParameters& parameters, co return res; } -uint64_t WalletSynchronizer::processMyInputs(const cryptonote::transaction& tx) { +uint64_t WalletSynchronizer::processMyInputs(const cryptonote::Transaction& tx) { uint64_t money = 0; // check all outputs for spending (compare key images) for (auto& in: tx.vin) { - if(in.type() != typeid(cryptonote::txin_to_key)) + if(in.type() != typeid(cryptonote::TransactionInputToKey)) continue; size_t idx; - if (!m_transferDetails.getTransferDetailsIdxByKeyImage(boost::get(in).k_image, idx)) + if (!m_transferDetails.getTransferDetailsIdxByKeyImage(boost::get(in).keyImage, idx)) continue; - money += boost::get(in).amount; + money += boost::get(in).amount; TransferDetails& td = m_transferDetails.getTransferDetails(idx); td.spent = true; @@ -349,7 +352,7 @@ uint64_t WalletSynchronizer::processMyInputs(const cryptonote::transaction& tx) return money; } -void WalletSynchronizer::fillGetTransactionOutsGlobalIndicesRequest(ProcessParameters& parameters, const cryptonote::transaction& tx, +void WalletSynchronizer::fillGetTransactionOutsGlobalIndicesRequest(ProcessParameters& parameters, const cryptonote::Transaction& tx, const std::vector& outs, const crypto::public_key& publicKey, uint64_t height) { crypto::hash txid = cryptonote::get_transaction_hash(tx); @@ -363,14 +366,14 @@ void WalletSynchronizer::fillGetTransactionOutsGlobalIndicesRequest(ProcessParam postGetTransactionOutsGlobalIndicesRequest(parameters, txid, insert_result.first->second.globalIndices, height); } -void WalletSynchronizer::updateTransactionsCache(ProcessParameters& parameters, const cryptonote::transaction& tx, uint64_t myOuts, uint64_t myInputs, uint64_t height, bool isCoinbase, uint64_t timestamp) { +void WalletSynchronizer::updateTransactionsCache(ProcessParameters& parameters, const cryptonote::Transaction& tx, uint64_t myOuts, uint64_t myInputs, uint64_t height, bool isCoinbase, uint64_t timestamp) { uint64_t allOuts = countOverallTxOutputs(tx); uint64_t allInputs = countOverallTxInputs(tx); TransactionId foundTx = m_transactionsCache.findTransactionByHash(cryptonote::get_transaction_hash(tx)); if (foundTx == INVALID_TRANSACTION_ID) { - Transaction transaction; + TransactionInfo transaction; transaction.firstTransferId = INVALID_TRANSFER_ID; transaction.transferCount = 0; transaction.totalAmount = myOuts - myInputs; @@ -386,7 +389,7 @@ void WalletSynchronizer::updateTransactionsCache(ProcessParameters& parameters, } else { - Transaction& transaction = m_transactionsCache.getTransaction(foundTx); + TransactionInfo& transaction = m_transactionsCache.getTransaction(foundTx); transaction.blockHeight = height; transaction.timestamp = timestamp; transaction.isCoinbase = isCoinbase; @@ -395,14 +398,14 @@ void WalletSynchronizer::updateTransactionsCache(ProcessParameters& parameters, } } -void WalletSynchronizer::processUnconfirmed(ProcessParameters& parameters, const cryptonote::transaction& tx, uint64_t height, uint64_t timestamp) { +void WalletSynchronizer::processUnconfirmed(ProcessParameters& parameters, const cryptonote::Transaction& tx, uint64_t height, uint64_t timestamp) { TransactionId id; crypto::hash hash = get_transaction_hash(tx); if (!m_unconfirmedTransactions.findTransactionId(hash, id)) return; - Transaction& tr = m_transactionsCache.getTransaction(id); + TransactionInfo& tr = m_transactionsCache.getTransaction(id); tr.blockHeight = height; tr.timestamp = timestamp; @@ -430,7 +433,7 @@ void WalletSynchronizer::handleTransactionOutGlobalIndicesResponse(std::shared_p return; } - cryptonote::transaction& tx = it->second.transaction; + cryptonote::Transaction& tx = it->second.transaction; std::vector& outs = it->second.requestedOuts; crypto::public_key& tx_pub_key = it->second.transactionPubKey; std::vector& global_indices = it->second.globalIndices; @@ -444,9 +447,9 @@ void WalletSynchronizer::handleTransactionOutGlobalIndicesResponse(std::shared_p td.globalOutputIndex = global_indices[o]; td.tx = tx; td.spent = false; - cryptonote::keypair in_ephemeral; + cryptonote::KeyPair in_ephemeral; cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, o, in_ephemeral, td.keyImage); - throwIf(in_ephemeral.pub != boost::get(tx.vout[o].target).key, cryptonote::error::INTERNAL_WALLET_ERROR); + throwIf(in_ephemeral.pub != boost::get(tx.vout[o].target).key, cryptonote::error::INTERNAL_WALLET_ERROR); m_transferDetails.addTransferDetails(td); } diff --git a/src/wallet/WalletSynchronizer.h b/src/wallet/WalletSynchronizer.h index 64886d48..9b69cb6c 100644 --- a/src/wallet/WalletSynchronizer.h +++ b/src/wallet/WalletSynchronizer.h @@ -65,17 +65,17 @@ private: bool processNewBlocks(ProcessParameters& parameters); NextBlockAction handleNewBlockchainEntry(ProcessParameters& parameters, cryptonote::block_complete_entry& blockEntry, uint64_t height); - bool processNewBlockchainEntry(ProcessParameters& parameters, cryptonote::block_complete_entry& blockEntry, const cryptonote::block& b, crypto::hash& blockId, uint64_t height); - bool processNewTransaction(ProcessParameters& parameters, const cryptonote::transaction& tx, uint64_t height, bool isCoinbase, uint64_t timestamp); - bool processMinersTx(ProcessParameters& parameters, const cryptonote::transaction& tx, uint64_t height, uint64_t timestamp); - void processUnconfirmed(ProcessParameters& parameters, const cryptonote::transaction& tx, uint64_t height, uint64_t timestamp); - uint64_t processMyInputs(const cryptonote::transaction& tx); - void updateTransactionsCache(ProcessParameters& parameters, const cryptonote::transaction& tx, uint64_t myOuts, uint64_t myInputs, uint64_t height, + bool processNewBlockchainEntry(ProcessParameters& parameters, cryptonote::block_complete_entry& blockEntry, const cryptonote::Block& b, crypto::hash& blockId, uint64_t height); + bool processNewTransaction(ProcessParameters& parameters, const cryptonote::Transaction& tx, uint64_t height, bool isCoinbase, uint64_t timestamp); + bool processMinersTx(ProcessParameters& parameters, const cryptonote::Transaction& tx, uint64_t height, uint64_t timestamp); + void processUnconfirmed(ProcessParameters& parameters, const cryptonote::Transaction& tx, uint64_t height, uint64_t timestamp); + uint64_t processMyInputs(const cryptonote::Transaction& tx); + void updateTransactionsCache(ProcessParameters& parameters, const cryptonote::Transaction& tx, uint64_t myOuts, uint64_t myInputs, uint64_t height, bool isCoinbase, uint64_t timestamp); void detachBlockchain(uint64_t height); void refreshBalance(std::deque >& events); - void fillGetTransactionOutsGlobalIndicesRequest(ProcessParameters& parameters, const cryptonote::transaction& tx, + void fillGetTransactionOutsGlobalIndicesRequest(ProcessParameters& parameters, const cryptonote::Transaction& tx, const std::vector& outs, const crypto::public_key& publicKey, uint64_t height); void postGetTransactionOutsGlobalIndicesRequest(ProcessParameters& parameters, const crypto::hash& hash, std::vector& outsGlobalIndices, uint64_t height); std::shared_ptr makeGetNewBlocksRequest(std::shared_ptr context); diff --git a/src/wallet/WalletTransactionSender.cpp b/src/wallet/WalletTransactionSender.cpp index afdf5aac..31fc1872 100644 --- a/src/wallet/WalletTransactionSender.cpp +++ b/src/wallet/WalletTransactionSender.cpp @@ -15,6 +15,11 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . +// epee +#include "misc_language.h" + +#include "cryptonote_core/account.h" + #include "WalletTransactionSender.h" #include "WalletUtils.h" @@ -33,7 +38,7 @@ uint64_t countNeededMoney(uint64_t fee, const std::vector& return needed_money; } -void createChangeDestinations(const cryptonote::account_public_address& address, uint64_t neededMoney, uint64_t foundMoney, cryptonote::tx_destination_entry& changeDts) { +void createChangeDestinations(const cryptonote::AccountPublicAddress& address, uint64_t neededMoney, uint64_t foundMoney, cryptonote::tx_destination_entry& changeDts) { if (neededMoney < foundMoney) { changeDts.addr = address; changeDts.amount = foundMoney - neededMoney; @@ -41,7 +46,7 @@ void createChangeDestinations(const cryptonote::account_public_address& address, } void constructTx(const cryptonote::account_keys keys, const std::vector& sources, const std::vector& splittedDests, - const std::string& extra, uint64_t unlockTimestamp, uint64_t sizeLimit, cryptonote::transaction& tx) { + const std::string& extra, uint64_t unlockTimestamp, uint64_t sizeLimit, cryptonote::Transaction& tx) { std::vector extraVec; extraVec.reserve(extra.size()); std::for_each(extra.begin(), extra.end(), [&extraVec] (const char el) { extraVec.push_back(el);}); @@ -51,7 +56,7 @@ void constructTx(const cryptonote::account_keys keys, const std::vector= sizeLimit, cryptonote::error::TRANSACTION_SIZE_TOO_BIG); } -void fillTransactionHash(const cryptonote::transaction& tx, CryptoNote::TransactionHash& hash) { +void fillTransactionHash(const cryptonote::Transaction& tx, CryptoNote::TransactionHash& hash) { crypto::hash h = cryptonote::get_transaction_hash(tx); memcpy(hash.data(), reinterpret_cast(&h), hash.size()); } @@ -60,15 +65,16 @@ void fillTransactionHash(const cryptonote::transaction& tx, CryptoNote::Transact namespace CryptoNote { -WalletTransactionSender::WalletTransactionSender(WalletUserTransactionsCache& transactionsCache, WalletTxSendingState& sendingTxsStates, - WalletTransferDetails& transferDetails, WalletUnconfirmedTransactions& unconfirmedTransactions): +WalletTransactionSender::WalletTransactionSender(const cryptonote::Currency& currency, WalletUserTransactionsCache& transactionsCache, + WalletTxSendingState& sendingTxsStates, WalletTransferDetails& transferDetails, WalletUnconfirmedTransactions& unconfirmedTransactions): + m_currency(currency), m_transactionsCache(transactionsCache), m_sendingTxsStates(sendingTxsStates), m_transferDetails(transferDetails), m_unconfirmedTransactions(unconfirmedTransactions), m_isInitialized(false), m_isStoping(false) { - m_upperTransactionSizeLimit = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE*2 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + m_upperTransactionSizeLimit = (m_currency.blockGrantedFullRewardZone() * 125) / 100 - m_currency.minerTxBlobReservedSize(); } void WalletTransactionSender::init(cryptonote::account_keys keys) { @@ -84,6 +90,19 @@ void WalletTransactionSender::stop() { m_isStoping = true; } +bool WalletTransactionSender::validateDestinationAddress(const std::string& address) { + cryptonote::AccountPublicAddress ignore; + return m_currency.parseAccountAddressString(address, ignore); +} + +void WalletTransactionSender::validateTransfersAddresses(const std::vector& transfers) { + for (const Transfer& tr: transfers) { + if (!validateDestinationAddress(tr.address)) { + throw std::system_error(make_error_code(cryptonote::error::BAD_ADDRESS)); + } + } +} + std::shared_ptr WalletTransactionSender::makeSendRequest(TransactionId& transactionId, std::deque >& events, const std::vector& transfers, uint64_t fee, const std::string& extra, uint64_t mixIn, uint64_t unlockTimestamp) { if (!m_isInitialized) @@ -91,14 +110,18 @@ std::shared_ptr WalletTransactionSender::makeSendRequest(Transact using namespace cryptonote; - std::shared_ptr context = std::make_shared(); throwIf(transfers.empty(), cryptonote::error::ZERO_DESTINATION); + validateTransfersAddresses(transfers); + uint64_t neededMoney = countNeededMoney(fee, transfers); + + std::shared_ptr context = std::make_shared(); + + context->foundMoney = m_transferDetails.selectTransfersToSend(neededMoney, 0 == mixIn, context->dustPolicy.dustThreshold, context->selectedTransfers); + throwIf(context->foundMoney < neededMoney, cryptonote::error::WRONG_AMOUNT); TransferId firstTransferId = m_transactionsCache.insertTransfers(transfers); - uint64_t neededMoney = countNeededMoney(fee, transfers); - - Transaction transaction; + TransactionInfo transaction; transaction.firstTransferId = firstTransferId; transaction.transferCount = transfers.size(); transaction.totalAmount = neededMoney; @@ -116,9 +139,6 @@ std::shared_ptr WalletTransactionSender::makeSendRequest(Transact context->unlockTimestamp = unlockTimestamp; context->mixIn = mixIn; - context->foundMoney = m_transferDetails.selectTransfersToSend(neededMoney, 0 == mixIn, context->dustPolicy.dustThreshold, context->selectedTransfers); - throwIf(context->foundMoney < neededMoney, cryptonote::error::WRONG_AMOUNT); - if(context->mixIn) { std::shared_ptr request = makeGetRandomOutsRequest(context); return request; @@ -173,7 +193,7 @@ std::shared_ptr WalletTransactionSender::doSendTransaction(std::s try { - Transaction& transaction = m_transactionsCache.getTransaction(context->transactionId); + TransactionInfo& transaction = m_transactionsCache.getTransaction(context->transactionId); std::vector sources; prepareInputs(context->selectedTransfers, context->outs, sources, context->mixIn); @@ -184,7 +204,7 @@ std::shared_ptr WalletTransactionSender::doSendTransaction(std::s std::vector splittedDests; splitDestinations(transaction.firstTransferId, transaction.transferCount, changeDts, context->dustPolicy, splittedDests); - cryptonote::transaction tx; + cryptonote::Transaction tx; constructTx(m_keys, sources, splittedDests, transaction.extra, context->unlockTimestamp, m_upperTransactionSizeLimit, tx); fillTransactionHash(tx, transaction.hash); @@ -241,8 +261,8 @@ void WalletTransactionSender::digitSplitStrategy(TransferId firstTransferId, siz for (TransferId idx = firstTransferId; idx < firstTransferId + transfersCount; ++idx) { Transfer& de = m_transactionsCache.getTransfer(idx); - cryptonote::account_public_address addr; - if (!cryptonote::get_account_address_from_str(addr, de.address)) { + cryptonote::AccountPublicAddress addr; + if (!m_currency.parseAccountAddressString(de.address, addr)) { throw std::system_error(make_error_code(cryptonote::error::BAD_ADDRESS)); } @@ -286,7 +306,7 @@ void WalletTransactionSender::prepareInputs(const std::list& selectedTra cryptonote::tx_source_entry::output_entry real_oe; real_oe.first = td.globalOutputIndex; - real_oe.second = boost::get(td.tx.vout[td.internalOutputIndex].target).key; + real_oe.second = boost::get(td.tx.vout[td.internalOutputIndex].target).key; auto interted_it = src.outputs.insert(it_to_insert, real_oe); diff --git a/src/wallet/WalletTransactionSender.h b/src/wallet/WalletTransactionSender.h index 87fd20d7..e4062662 100644 --- a/src/wallet/WalletTransactionSender.h +++ b/src/wallet/WalletTransactionSender.h @@ -17,6 +17,9 @@ #pragma once +#include "cryptonote_core/account.h" +#include "cryptonote_core/Currency.h" + #include "INode.h" #include "WalletSendTransactionContext.h" #include "WalletUserTransactionsCache.h" @@ -29,8 +32,8 @@ namespace CryptoNote { class WalletTransactionSender { public: - WalletTransactionSender(WalletUserTransactionsCache& transactionsCache, WalletTxSendingState& sendingTxsStates, WalletTransferDetails& transferDetails, - WalletUnconfirmedTransactions& unconfirmedTransactions); + WalletTransactionSender(const cryptonote::Currency& currency, WalletUserTransactionsCache& transactionsCache, + WalletTxSendingState& sendingTxsStates, WalletTransferDetails& transferDetails, WalletUnconfirmedTransactions& unconfirmedTransactions); void init(cryptonote::account_keys keys); void stop(); @@ -52,7 +55,10 @@ private: void relayTransactionCallback(TransactionId txId, std::deque >& events, boost::optional >& nextRequest, std::error_code ec); void notifyBalanceChanged(std::deque >& events); + void validateTransfersAddresses(const std::vector& transfers); + bool validateDestinationAddress(const std::string& address); + const cryptonote::Currency& m_currency; cryptonote::account_keys m_keys; WalletUserTransactionsCache& m_transactionsCache; WalletTxSendingState& m_sendingTxsStates; diff --git a/src/wallet/WalletTransferDetails.cpp b/src/wallet/WalletTransferDetails.cpp index 9e3b596e..0d3ea404 100644 --- a/src/wallet/WalletTransferDetails.cpp +++ b/src/wallet/WalletTransferDetails.cpp @@ -50,7 +50,8 @@ T popRandomValue(URNG& randomGenerator, std::vector& vec) { namespace CryptoNote { -WalletTransferDetails::WalletTransferDetails(const std::vector& blockchain) : m_blockchain(blockchain) { +WalletTransferDetails::WalletTransferDetails(const cryptonote::Currency& currency, const std::vector& blockchain) : + m_currency(currency), m_blockchain(blockchain) { } WalletTransferDetails::~WalletTransferDetails() { @@ -76,30 +77,21 @@ bool WalletTransferDetails::getTransferDetailsIdxByKeyImage(const crypto::key_im return true; } -bool WalletTransferDetails::isTxSpendtimeUnlocked(uint64_t unlockTime) const -{ - if(unlockTime < CRYPTONOTE_MAX_BLOCK_NUMBER) { - //interpret as block index - if(m_blockchain.size()-1 + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS >= unlockTime) - return true; - else - return false; - } - else - { - //interpret as time +bool WalletTransferDetails::isTxSpendtimeUnlocked(uint64_t unlockTime) const { + if (unlockTime < m_currency.maxBlockHeight()) { + // interpret as block index + return m_blockchain.size()-1 + m_currency.lockedTxAllowedDeltaBlocks() >= unlockTime; + } else { + // interpret as time uint64_t current_time = static_cast(time(NULL)); - if(current_time + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS >= unlockTime) - return true; - else - return false; + return current_time + m_currency.lockedTxAllowedDeltaSeconds() >= unlockTime; } return false; } bool WalletTransferDetails::isTransferUnlocked(const TransferDetails& td) const { - if(!isTxSpendtimeUnlocked(td.tx.unlock_time)) + if(!isTxSpendtimeUnlocked(td.tx.unlockTime)) return false; if(td.blockHeight + DEFAULT_TX_SPENDABLE_AGE > m_blockchain.size()) diff --git a/src/wallet/WalletTransferDetails.h b/src/wallet/WalletTransferDetails.h index 911b4d49..1f9ce4e9 100644 --- a/src/wallet/WalletTransferDetails.h +++ b/src/wallet/WalletTransferDetails.h @@ -20,6 +20,7 @@ #include #include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_core/Currency.h" #include "IWallet.h" namespace CryptoNote { @@ -27,7 +28,7 @@ namespace CryptoNote { struct TransferDetails { uint64_t blockHeight; - cryptonote::transaction tx; + cryptonote::Transaction tx; size_t internalOutputIndex; uint64_t globalOutputIndex; bool spent; @@ -42,7 +43,7 @@ struct TransferDetails class WalletTransferDetails { public: - WalletTransferDetails(const std::vector& blockchain); + WalletTransferDetails(const cryptonote::Currency& currency, const std::vector& blockchain); ~WalletTransferDetails(); TransferDetails& getTransferDetails(size_t idx); @@ -72,6 +73,7 @@ private: typedef std::unordered_map KeyImagesContainer; KeyImagesContainer m_keyImages; + const cryptonote::Currency& m_currency; const std::vector& m_blockchain; }; diff --git a/src/wallet/WalletUnconfirmedTransactions.cpp b/src/wallet/WalletUnconfirmedTransactions.cpp index 0ac37f63..b0ed23df 100644 --- a/src/wallet/WalletUnconfirmedTransactions.cpp +++ b/src/wallet/WalletUnconfirmedTransactions.cpp @@ -35,7 +35,7 @@ void WalletUnconfirmedTransactions::erase(const crypto::hash& hash) { m_unconfirmedTxs.erase(hash); } -void WalletUnconfirmedTransactions::add(const cryptonote::transaction& tx, +void WalletUnconfirmedTransactions::add(const cryptonote::Transaction& tx, TransactionId transactionId, uint64_t change_amount) { UnconfirmedTransferDetails& utd = m_unconfirmedTxs[cryptonote::get_transaction_hash(tx)]; diff --git a/src/wallet/WalletUnconfirmedTransactions.h b/src/wallet/WalletUnconfirmedTransactions.h index addcfffa..67275dec 100644 --- a/src/wallet/WalletUnconfirmedTransactions.h +++ b/src/wallet/WalletUnconfirmedTransactions.h @@ -29,7 +29,7 @@ namespace CryptoNote { struct UnconfirmedTransferDetails { - cryptonote::transaction tx; + cryptonote::Transaction tx; uint64_t change; time_t sentTime; TransactionId transactionId; @@ -46,7 +46,7 @@ public: bool findTransactionId(const crypto::hash& hash, TransactionId& id); void erase(const crypto::hash& hash); - void add(const cryptonote::transaction& tx, TransactionId transactionId, uint64_t change_amount); + void add(const cryptonote::Transaction& tx, TransactionId transactionId, uint64_t change_amount); uint64_t countPendingBalance() const; diff --git a/src/wallet/WalletUserTransactionsCache.cpp b/src/wallet/WalletUserTransactionsCache.cpp index f14cf9a8..cb46c0c6 100644 --- a/src/wallet/WalletUserTransactionsCache.cpp +++ b/src/wallet/WalletUserTransactionsCache.cpp @@ -41,7 +41,7 @@ TransactionId WalletUserTransactionsCache::findTransactionByTransferId(TransferI { TransactionId id; for (id = 0; id < m_transactions.size(); ++id) { - const Transaction& tx = m_transactions[id]; + const TransactionInfo& tx = m_transactions[id]; if (tx.firstTransferId == INVALID_TRANSFER_ID || tx.transferCount == 0) continue; @@ -56,7 +56,7 @@ TransactionId WalletUserTransactionsCache::findTransactionByTransferId(TransferI return id; } -bool WalletUserTransactionsCache::getTransaction(TransactionId transactionId, Transaction& transaction) const +bool WalletUserTransactionsCache::getTransaction(TransactionId transactionId, TransactionInfo& transaction) const { if (transactionId >= m_transactions.size()) return false; @@ -76,13 +76,13 @@ bool WalletUserTransactionsCache::getTransfer(TransferId transferId, Transfer& t return true; } -TransactionId WalletUserTransactionsCache::insertTransaction(Transaction&& transaction) { - m_transactions.emplace_back(transaction); +TransactionId WalletUserTransactionsCache::insertTransaction(TransactionInfo&& Transaction) { + m_transactions.emplace_back(Transaction); return m_transactions.size() - 1; } TransactionId WalletUserTransactionsCache::findTransactionByHash(const crypto::hash& hash) { - auto it = std::find_if(m_transactions.begin(), m_transactions.end(), [&hash] (const Transaction& tx) { return hashesEqual(tx.hash, hash); }); + auto it = std::find_if(m_transactions.begin(), m_transactions.end(), [&hash] (const TransactionInfo& tx) { return hashesEqual(tx.hash, hash); }); if (it == m_transactions.end()) return CryptoNote::INVALID_TRANSACTION_ID; @@ -92,7 +92,7 @@ TransactionId WalletUserTransactionsCache::findTransactionByHash(const crypto::h void WalletUserTransactionsCache::detachTransactions(uint64_t height) { for (size_t id = 0; id < m_transactions.size(); ++id) { - Transaction& tx = m_transactions[id]; + TransactionInfo& tx = m_transactions[id]; if (tx.blockHeight >= height) { tx.blockHeight = UNCONFIRMED_TRANSACTION_HEIGHT; tx.timestamp = 0; @@ -100,7 +100,7 @@ void WalletUserTransactionsCache::detachTransactions(uint64_t height) { } } -Transaction& WalletUserTransactionsCache::getTransaction(TransactionId transactionId) { +TransactionInfo& WalletUserTransactionsCache::getTransaction(TransactionId transactionId) { return m_transactions.at(transactionId); } @@ -116,7 +116,7 @@ void WalletUserTransactionsCache::getGoodItems(bool saveDetailed, UserTransactio } else { - const Transaction& t = m_transactions[txId]; + const TransactionInfo& t = m_transactions[txId]; if (t.firstTransferId != INVALID_TRANSFER_ID) offset += t.transferCount; } @@ -125,7 +125,7 @@ void WalletUserTransactionsCache::getGoodItems(bool saveDetailed, UserTransactio void WalletUserTransactionsCache::getGoodTransaction(TransactionId txId, size_t offset, bool saveDetailed, UserTransactions& transactions, UserTransfers& transfers) { transactions.push_back(m_transactions[txId]); - Transaction& tx = transactions.back(); + TransactionInfo& tx = transactions.back(); if (!saveDetailed) { tx.firstTransferId = INVALID_TRANSFER_ID; @@ -157,7 +157,7 @@ void WalletUserTransactionsCache::getGoodTransfers(UserTransfers& transfers) { } void WalletUserTransactionsCache::getTransfersByTx(TransactionId id, UserTransfers& transfers) { - const Transaction& tx = m_transactions[id]; + const TransactionInfo& tx = m_transactions[id]; if (tx.firstTransferId != INVALID_TRANSFER_ID) { UserTransfers::const_iterator first = m_transfers.begin() + tx.firstTransferId; diff --git a/src/wallet/WalletUserTransactionsCache.h b/src/wallet/WalletUserTransactionsCache.h index dea85167..11644c8d 100644 --- a/src/wallet/WalletUserTransactionsCache.h +++ b/src/wallet/WalletUserTransactionsCache.h @@ -39,12 +39,12 @@ public: TransactionId findTransactionByTransferId(TransferId transferId) const; - bool getTransaction(TransactionId transactionId, Transaction& transaction) const; - Transaction& getTransaction(TransactionId transactionId); + bool getTransaction(TransactionId transactionId, TransactionInfo& transaction) const; + TransactionInfo& getTransaction(TransactionId transactionId); bool getTransfer(TransferId transferId, Transfer& transfer) const; Transfer& getTransfer(TransferId transferId); - TransactionId insertTransaction(Transaction&& transaction); + TransactionId insertTransaction(TransactionInfo&& Transaction); TransferId insertTransfers(const std::vector& transfers); TransactionId findTransactionByHash(const crypto::hash& hash); @@ -52,7 +52,7 @@ public: private: typedef std::vector UserTransfers; - typedef std::vector UserTransactions; + typedef std::vector UserTransactions; void getGoodItems(bool saveDetailed, UserTransactions& transactions, UserTransfers& transfers); void getGoodTransaction(TransactionId txId, size_t offset, bool saveDetailed, UserTransactions& transactions, UserTransfers& transfers); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index f737289d..f7dc0578 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -15,25 +15,30 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . -#include -#include - -#include -#include "include_base_utils.h" -using namespace epee; - #include "wallet2.h" + +#include + +#include +#include +#include + +// epee +#include "include_base_utils.h" +#include "misc_language.h" +#include "profile_tools.h" + +#include "common/boost_serialization_helper.h" +#include "crypto/crypto.h" +#include "cryptonote_core/AccountKVSerialization.h" #include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_core/cryptonote_basic_impl.h" #include "cryptonote_protocol/blobdatatype.h" #include "rpc/core_rpc_server_commands_defs.h" -#include "misc_language.h" -#include "cryptonote_core/cryptonote_basic_impl.h" -#include "common/boost_serialization_helper.h" -#include "profile_tools.h" -#include "crypto/crypto.h" #include "serialization/binary_utils.h" using namespace cryptonote; +using namespace epee; namespace { @@ -55,17 +60,13 @@ void do_prepare_file_names(const std::string& file_path, std::string& keys_file, namespace tools { //---------------------------------------------------------------------------------------------------- -void wallet2::init(const std::string& daemon_address, uint64_t upper_transaction_size_limit) +void wallet2::init(const std::string& daemon_address) { - m_upper_transaction_size_limit = upper_transaction_size_limit; m_daemon_address = daemon_address; } //---------------------------------------------------------------------------------------------------- -void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_t height) -{ +bool wallet2::processNewTransaction(TxQueue& queue, const cryptonote::Transaction& tx, uint64_t height, const crypto::hash& bl_id) { process_unconfirmed(tx); - std::vector outs; - uint64_t tx_money_got_in_outs = 0; std::vector tx_extra_fields; if(!parse_tx_extra(tx.extra, tx_extra_fields)) @@ -80,14 +81,26 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ LOG_PRINT_L0("Public key wasn't found in the transaction extra. Skipping transaction " << get_transaction_hash(tx)); if(0 != m_callback) m_callback->on_skip_transaction(height, tx); - return; + return false; } - crypto::public_key tx_pub_key = pub_key_field.pub_key; - bool r = lookup_acc_outs(m_account.get_keys(), tx, tx_pub_key, outs, tx_money_got_in_outs); - THROW_WALLET_EXCEPTION_IF(!r, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); + TxItem txItem = { tx, height, bl_id, pub_key_field.pub_key, std::move(tx_extra_fields) }; + queue.push(std::unique_ptr(new TxItem(std::move(txItem)))); + return true; +} - if(!outs.empty() && tx_money_got_in_outs) +//---------------------------------------------------------------------------------------------------- +void wallet2::processCheckedTransaction(const TxItem& item) { + + const cryptonote::Transaction& tx = item.tx; + const std::vector& outs = item.outs; + const std::vector& tx_extra_fields = item.txExtraFields; + + uint64_t tx_money_got_in_outs = item.txMoneyGotInOuts; + uint64_t height = item.height; + + + if (!outs.empty() && tx_money_got_in_outs) { //good news - got money! take care about it //usually we have only one transfer for user in transaction @@ -102,8 +115,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ "transactions outputs size=" + std::to_string(tx.vout.size()) + " not match with COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES response size=" + std::to_string(res.o_indexes.size())); - BOOST_FOREACH(size_t o, outs) - { + for (size_t o : outs) { THROW_WALLET_EXCEPTION_IF(tx.vout.size() <= o, error::wallet_internal_error, "wrong out in transaction: internal index=" + std::to_string(o) + ", total_outs=" + std::to_string(tx.vout.size())); @@ -114,15 +126,16 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ td.m_global_output_index = res.o_indexes[o]; td.m_tx = tx; td.m_spent = false; - cryptonote::keypair in_ephemeral; - cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, o, in_ephemeral, td.m_key_image); - THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != boost::get(tx.vout[o].target).key, + cryptonote::KeyPair in_ephemeral; + cryptonote::generate_key_image_helper(m_account.get_keys(), item.txPubKey, o, in_ephemeral, td.m_key_image); + THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != boost::get(tx.vout[o].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); - m_key_images[td.m_key_image] = m_transfers.size()-1; - LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx)); - if (0 != m_callback) + m_key_images[td.m_key_image] = m_transfers.size() - 1; + LOG_PRINT_L0("Received money: " << m_currency.formatAmount(td.amount()) << ", with tx: " << get_transaction_hash(tx)); + if (0 != m_callback) { m_callback->on_money_received(height, td.m_tx, td.m_internal_output_index); + } } } @@ -130,17 +143,17 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ // check all outputs for spending (compare key images) BOOST_FOREACH(auto& in, tx.vin) { - if(in.type() != typeid(cryptonote::txin_to_key)) - continue; - auto it = m_key_images.find(boost::get(in).k_image); - if(it != m_key_images.end()) - { - LOG_PRINT_L0("Spent money: " << print_money(boost::get(in).amount) << ", with tx: " << get_transaction_hash(tx)); - tx_money_spent_in_ins += boost::get(in).amount; - transfer_details& td = m_transfers[it->second]; - td.m_spent = true; - if (0 != m_callback) - m_callback->on_money_spent(height, td.m_tx, td.m_internal_output_index, tx); + if (in.type() == typeid(cryptonote::TransactionInputToKey)) { + auto it = m_key_images.find(boost::get(in).keyImage); + if (it != m_key_images.end()) { + LOG_PRINT_L0("Spent money: " << m_currency.formatAmount(boost::get(in).amount) << + ", with tx: " << get_transaction_hash(tx)); + tx_money_spent_in_ins += boost::get(in).amount; + transfer_details& td = m_transfers[it->second]; + td.m_spent = true; + if (0 != m_callback) + m_callback->on_money_spent(height, td.m_tx, td.m_internal_output_index, tx); + } } } @@ -148,65 +161,104 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) { crypto::hash payment_id; - if(get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) + if (get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) { uint64_t received = (tx_money_spent_in_ins < tx_money_got_in_outs) ? tx_money_got_in_outs - tx_money_spent_in_ins : 0; if (0 < received && null_hash != payment_id) { payment_details payment; - payment.m_tx_hash = cryptonote::get_transaction_hash(tx); - payment.m_amount = received; + payment.m_tx_hash = cryptonote::get_transaction_hash(tx); + payment.m_amount = received; payment.m_block_height = height; - payment.m_unlock_time = tx.unlock_time; + payment.m_unlock_time = tx.unlockTime; m_payments.emplace(payment_id, payment); LOG_PRINT_L2("Payment found: " << payment_id << " / " << payment.m_tx_hash << " / " << payment.m_amount); } } } + } + //---------------------------------------------------------------------------------------------------- -void wallet2::process_unconfirmed(const cryptonote::transaction& tx) +void wallet2::process_unconfirmed(const cryptonote::Transaction& tx) { auto unconf_it = m_unconfirmed_txs.find(get_transaction_hash(tx)); if(unconf_it != m_unconfirmed_txs.end()) m_unconfirmed_txs.erase(unconf_it); } + //---------------------------------------------------------------------------------------------------- -void wallet2::process_new_blockchain_entry(const cryptonote::block& b, cryptonote::block_complete_entry& bche, crypto::hash& bl_id, uint64_t height) +bool wallet2::addNewBlockchainEntry(const crypto::hash& bl_id, uint64_t start_height, uint64_t current_index) { - //handle transactions from new block - THROW_WALLET_EXCEPTION_IF(height != m_blockchain.size(), error::wallet_internal_error, - "current_index=" + std::to_string(height) + ", m_blockchain.size()=" + std::to_string(m_blockchain.size())); + if (current_index < m_blockchain.size()) { + if (bl_id != m_blockchain[current_index]) { + //split detected here !!! + THROW_WALLET_EXCEPTION_IF(current_index == start_height, error::wallet_internal_error, + "wrong daemon response: split starts from the first block in response " + string_tools::pod_to_hex(bl_id) + + " (height " + std::to_string(start_height) + "), local block id at this height: " + + string_tools::pod_to_hex(m_blockchain[current_index])); - //optimization: seeking only for blocks that are not older then the wallet creation time plus 1 day. 1 day is for possible user incorrect time setup - if(b.timestamp + 60*60*24 > m_account.get_createtime()) - { - TIME_MEASURE_START(miner_tx_handle_time); - process_new_transaction(b.miner_tx, height); - TIME_MEASURE_FINISH(miner_tx_handle_time); - - TIME_MEASURE_START(txs_handle_time); - BOOST_FOREACH(auto& txblob, bche.txs) - { - cryptonote::transaction tx; - bool r = parse_and_validate_tx_from_blob(txblob, tx); - THROW_WALLET_EXCEPTION_IF(!r, error::tx_parse_error, txblob); - process_new_transaction(tx, height); + detach_blockchain(current_index); + } else { + LOG_PRINT_L2("Block is already in blockchain: " << string_tools::pod_to_hex(bl_id)); + return false; } - TIME_MEASURE_FINISH(txs_handle_time); - LOG_PRINT_L2("Processed block: " << bl_id << ", height " << height << ", " << miner_tx_handle_time + txs_handle_time << "(" << miner_tx_handle_time << "/" << txs_handle_time <<")ms"); - }else - { - LOG_PRINT_L2( "Skipped block by timestamp, height: " << height << ", block time " << b.timestamp << ", account time " << m_account.get_createtime()); } + + THROW_WALLET_EXCEPTION_IF(current_index != m_blockchain.size(), error::wallet_internal_error, + "current_index=" + std::to_string(current_index) + ", m_blockchain.size()=" + std::to_string(m_blockchain.size())); + m_blockchain.push_back(bl_id); ++m_local_bc_height; - if (0 != m_callback) - m_callback->on_new_block(height, b); + if (0 != m_callback) { + m_callback->on_new_block(current_index); + } + + return true; } + +size_t wallet2::processNewBlockchainEntry(TxQueue& queue, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height) +{ + size_t processedTransactions = 0; + + if (!bche.block.empty()) + { + cryptonote::Block b; + bool r = cryptonote::parse_and_validate_block_from_blob(bche.block, b); + THROW_WALLET_EXCEPTION_IF(!r, error::block_parse_error, bche.block); + + //optimization: seeking only for blocks that are not older then the wallet creation time plus 1 day. 1 day is for possible user incorrect time setup + if (b.timestamp + 60 * 60 * 24 > m_account.get_createtime()) + { + TIME_MEASURE_START(miner_tx_handle_time); + if(processNewTransaction(queue, b.minerTx, height, bl_id)) + ++processedTransactions; + TIME_MEASURE_FINISH(miner_tx_handle_time); + + TIME_MEASURE_START(txs_handle_time); + BOOST_FOREACH(auto& txblob, bche.txs) + { + cryptonote::Transaction tx; + bool r = parse_and_validate_tx_from_blob(txblob, tx); + THROW_WALLET_EXCEPTION_IF(!r, error::tx_parse_error, txblob); + if(processNewTransaction(queue, tx, height, bl_id)) + ++processedTransactions; + } + TIME_MEASURE_FINISH(txs_handle_time); + LOG_PRINT_L2("Processed block: " << bl_id << ", height " << height << ", " << miner_tx_handle_time + txs_handle_time << "(" << miner_tx_handle_time << "/" << txs_handle_time << ")ms"); + } else + { + LOG_PRINT_L2("Skipped block by timestamp, height: " << height << ", block time " << b.timestamp << ", account time " << m_account.get_createtime()); + } + } + + return processedTransactions; +} + + //---------------------------------------------------------------------------------------------------- -void wallet2::get_short_chain_history(std::list& ids) +void wallet2::get_short_chain_history(std::list& ids) const { size_t i = 0; size_t current_multiplier = 1; @@ -232,53 +284,109 @@ void wallet2::get_short_chain_history(std::list& ids) if(!genesis_included) ids.push_back(m_blockchain[0]); } + //---------------------------------------------------------------------------------------------------- -void wallet2::pull_blocks(size_t& blocks_added) +size_t wallet2::updateBlockchain(const cryptonote::COMMAND_RPC_QUERY_BLOCKS::response& res) { - blocks_added = 0; - cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::request req = AUTO_VAL_INIT(req); - cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::response res = AUTO_VAL_INIT(res); + size_t blocks_added = 0; + size_t current_index = res.start_height; + + // update local blockchain + for (const auto& item : res.items) { + if (addNewBlockchainEntry(item.block_id, res.start_height, current_index)) + ++blocks_added; + ++current_index; + } + + return blocks_added; +} + +//---------------------------------------------------------------------------------------------------- +void wallet2::processTransactions(const cryptonote::COMMAND_RPC_QUERY_BLOCKS::response& res) +{ + size_t checkingThreads = std::thread::hardware_concurrency(); + + if (checkingThreads == 0) + checkingThreads = 4; + + std::vector> futures; + + TxQueue incomingQueue(checkingThreads * 2); + TxQueue checkedQueue(checkingThreads * 2); + + std::atomic inputTx(0); + std::atomic checkedTx(0); + + futures.push_back(std::async(std::launch::async, [&] { + try { + size_t current_index = res.start_height; + for (const auto& item : res.items) { + inputTx += processNewBlockchainEntry(incomingQueue, item, item.block_id, current_index); + ++current_index; + } + incomingQueue.close(); + } catch (...) { + LOG_ERROR("Exception in pushing thread!"); + incomingQueue.close(); + throw; + } + })); + + GroupClose queueClose(checkedQueue, checkingThreads); + + for (size_t i = 0; i < checkingThreads; ++i) { + futures.push_back(std::async(std::launch::async, [&] { + TxQueueItem item; + while (incomingQueue.pop(item)) { + ++checkedTx; + lookup_acc_outs(m_account.get_keys(), item->tx, item->txPubKey, item->outs, item->txMoneyGotInOuts); + checkedQueue.push(std::move(item)); + } + queueClose.close(); + })); + } + + size_t txCount = 0; + + TxQueueItem item; + while (checkedQueue.pop(item)) { + processCheckedTransaction(*item); + ++txCount; + } + + for (auto& f : futures) { + f.get(); + } + + if (checkedQueue.size() > 0 || incomingQueue.size() > 0) { + LOG_ERROR("ERROR! Queues not empty. Incoming: " << incomingQueue.size() << " Checked: " << checkedQueue.size()); + } + + if (inputTx != txCount) { + LOG_ERROR("Failed to process some transactions. Incoming: " << inputTx << " Processed: " << txCount); + } +} + +//---------------------------------------------------------------------------------------------------- +cryptonote::COMMAND_RPC_QUERY_BLOCKS::response wallet2::queryBlocks(epee::net_utils::http::http_simple_client& client) +{ + cryptonote::COMMAND_RPC_QUERY_BLOCKS::request req = AUTO_VAL_INIT(req); + cryptonote::COMMAND_RPC_QUERY_BLOCKS::response res = AUTO_VAL_INIT(res); + get_short_chain_history(req.block_ids); - bool r = net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/getblocks.bin", req, res, m_http_client, WALLET_RCP_CONNECTION_TIMEOUT); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getblocks.bin"); - THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getblocks.bin"); + req.timestamp = m_account.get_createtime() - 60 * 60 * 24; // get full blocks starting from wallet creation time minus 1 day + + bool r = net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/queryblocks.bin", req, res, client, WALLET_RCP_CONNECTION_TIMEOUT); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "queryblocks.bin"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "queryblocks.bin"); THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_blocks_error, res.status); THROW_WALLET_EXCEPTION_IF(m_blockchain.size() <= res.start_height, error::wallet_internal_error, "wrong daemon response: m_start_height=" + std::to_string(res.start_height) + " not less than local blockchain size=" + std::to_string(m_blockchain.size())); - size_t current_index = res.start_height; - BOOST_FOREACH(auto& bl_entry, res.blocks) - { - cryptonote::block bl; - r = cryptonote::parse_and_validate_block_from_blob(bl_entry.block, bl); - THROW_WALLET_EXCEPTION_IF(!r, error::block_parse_error, bl_entry.block); - - crypto::hash bl_id = get_block_hash(bl); - if(current_index >= m_blockchain.size()) - { - process_new_blockchain_entry(bl, bl_entry, bl_id, current_index); - ++blocks_added; - } - else if(bl_id != m_blockchain[current_index]) - { - //split detected here !!! - THROW_WALLET_EXCEPTION_IF(current_index == res.start_height, error::wallet_internal_error, - "wrong daemon response: split starts from the first block in response " + string_tools::pod_to_hex(bl_id) + - " (height " + std::to_string(res.start_height) + "), local block id at this height: " + - string_tools::pod_to_hex(m_blockchain[current_index])); - - detach_blockchain(current_index); - process_new_blockchain_entry(bl, bl_entry, bl_id, current_index); - } - else - { - LOG_PRINT_L2("Block is already in blockchain: " << string_tools::pod_to_hex(bl_id)); - } - - ++current_index; - } + return res; } + //---------------------------------------------------------------------------------------------------- void wallet2::refresh() { @@ -300,14 +408,38 @@ void wallet2::refresh(size_t & blocks_fetched, bool& received_money) size_t try_count = 0; crypto::hash last_tx_hash_id = m_transfers.size() ? get_transaction_hash(m_transfers.back().m_tx) : null_hash; + std::future processingTask; + + epee::net_utils::http::http_simple_client queryClient; + + auto r = connectClient(queryClient); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "refresh"); + + auto startTime = std::chrono::high_resolution_clock::now(); + while(m_run.load(std::memory_order_relaxed)) { try { - pull_blocks(added_blocks); - blocks_fetched += added_blocks; - if(!added_blocks) + auto res = std::make_shared(queryBlocks(queryClient)); + + if (processingTask.valid()) + processingTask.get(); // sync with transaction processing + + added_blocks = updateBlockchain(*res); + + if (!added_blocks) { break; + } + + blocks_fetched += added_blocks; + + bool hasFullBlocks = std::any_of(res->items.begin(), res->items.end(), + [](const cryptonote::COMMAND_RPC_QUERY_BLOCKS::response_item& ri) { return !ri.block.empty(); }); + + if (hasFullBlocks) { + processingTask = std::async(std::launch::async, [res, this] { processTransactions(*res); }); + } } catch (const std::exception&) { @@ -324,10 +456,19 @@ void wallet2::refresh(size_t & blocks_fetched, bool& received_money) } } } + + if (processingTask.valid()) + processingTask.get(); + + auto duration = std::chrono::high_resolution_clock::now() - startTime; + if(last_tx_hash_id != (m_transfers.size() ? get_transaction_hash(m_transfers.back().m_tx) : null_hash)) received_money = true; - LOG_PRINT_L1("Refresh done, blocks received: " << blocks_fetched << ", balance: " << print_money(balance()) << ", unlocked: " << print_money(unlocked_balance())); + LOG_PRINT_L1("Refresh done, blocks received: " << blocks_fetched << + ", balance: " << m_currency.formatAmount(balance()) << + ", unlocked: " << m_currency.formatAmount(unlocked_balance())); + LOG_PRINT_L1("Time: " << std::chrono::duration(duration).count() << "s"); } //---------------------------------------------------------------------------------------------------- bool wallet2::refresh(size_t & blocks_fetched, bool& received_money, bool& ok) @@ -385,9 +526,7 @@ bool wallet2::clear() { m_blockchain.clear(); m_transfers.clear(); - cryptonote::block b; - cryptonote::generate_genesis_block(b); - m_blockchain.push_back(get_block_hash(b)); + m_blockchain.push_back(m_currency.genesisBlockHash()); m_local_bc_height = 1; return true; } @@ -395,7 +534,8 @@ bool wallet2::clear() bool wallet2::store_keys(const std::string& keys_file_name, const std::string& password) { std::string account_data; - bool r = epee::serialization::store_t_to_binary(m_account, account_data); + AccountBaseSerializer accountSerializer(m_account); + bool r = epee::serialization::store_t_to_binary(accountSerializer, account_data); CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet keys"); wallet2::keys_file_data keys_file_data = boost::value_initialized(); @@ -443,9 +583,10 @@ void wallet2::load_keys(const std::string& keys_file_name, const std::string& pa crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); const cryptonote::account_keys& keys = m_account.get_keys(); - r = epee::serialization::load_t_from_binary(m_account, account_data); - r = r && verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); - r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); + AccountBaseSerializer accountSerializer(m_account); + r = epee::serialization::load_t_from_binary(accountSerializer, account_data); + r = r && verify_keys(keys.m_view_secret_key, keys.m_account_address.m_viewPublicKey); + r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spendPublicKey); THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password); } //---------------------------------------------------------------------------------------------------- @@ -464,9 +605,11 @@ void wallet2::generate(const std::string& wallet_, const std::string& password) bool r = store_keys(m_keys_file, password); THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); - r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str()); + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_currency.accountAddressAsString(m_account)); if(!r) LOG_PRINT_RED_L0("String with address text not saved"); + m_blockchain.push_back(m_currency.genesisBlockHash()); + store(); } //---------------------------------------------------------------------------------------------------- @@ -504,13 +647,18 @@ bool wallet2::check_connection() { if(m_http_client.is_connected()) return true; + return connectClient(m_http_client); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::connectClient(epee::net_utils::http::http_simple_client& client) { net_utils::http::url_content u; net_utils::parse_url(m_daemon_address, u); - if(!u.port) + if (!u.port) u.port = RPC_DEFAULT_PORT; - return m_http_client.connect(u.host, std::to_string(u.port), WALLET_RCP_CONNECTION_TIMEOUT); + return client.connect(u.host, std::to_string(u.port), WALLET_RCP_CONNECTION_TIMEOUT); } + //---------------------------------------------------------------------------------------------------- void wallet2::load(const std::string& wallet_, const std::string& password) { @@ -522,7 +670,7 @@ void wallet2::load(const std::string& wallet_, const std::string& password) THROW_WALLET_EXCEPTION_IF(e || !exists, error::file_not_found, m_keys_file); load_keys(m_keys_file, password); - LOG_PRINT_L0("Loaded wallet keys file, with public address: " << m_account.get_public_address_str()); + LOG_PRINT_L0("Loaded wallet keys file, with public address: " << m_currency.accountAddressAsString(m_account)); //keys loaded ok! //try to load wallet file. but even if we failed, it is not big problem @@ -530,21 +678,22 @@ void wallet2::load(const std::string& wallet_, const std::string& password) { LOG_PRINT_L0("file not found: " << m_wallet_file << ", starting with empty blockchain"); m_account_public_address = m_account.get_keys().m_account_address; - return; + } else { + bool r = tools::unserialize_obj_from_file(*this, m_wallet_file); + THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, m_wallet_file); + THROW_WALLET_EXCEPTION_IF( + m_account_public_address.m_spendPublicKey != m_account.get_keys().m_account_address.m_spendPublicKey || + m_account_public_address.m_viewPublicKey != m_account.get_keys().m_account_address.m_viewPublicKey, + error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file); } - bool r = tools::unserialize_obj_from_file(*this, m_wallet_file); - THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, m_wallet_file); - THROW_WALLET_EXCEPTION_IF( - m_account_public_address.m_spend_public_key != m_account.get_keys().m_account_address.m_spend_public_key || - m_account_public_address.m_view_public_key != m_account.get_keys().m_account_address.m_view_public_key, - error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file); - if(m_blockchain.empty()) - { - cryptonote::block b; - cryptonote::generate_genesis_block(b); - m_blockchain.push_back(get_block_hash(b)); + if (m_blockchain.empty()) { + m_blockchain.push_back(m_currency.genesisBlockHash()); + } else { + THROW_WALLET_EXCEPTION_IF(m_blockchain[0] != m_currency.genesisBlockHash(), error::wallet_internal_error, + "Genesis block missmatch. You probably use wallet without testnet flag with blockchain from test network or vice versa"); } + m_local_bc_height = m_blockchain.size(); } //---------------------------------------------------------------------------------------------------- @@ -593,7 +742,7 @@ void wallet2::get_payments(const crypto::hash& payment_id, std::list m_blockchain.size()) @@ -602,23 +751,14 @@ bool wallet2::is_transfer_unlocked(const transfer_details& td) const return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::is_tx_spendtime_unlocked(uint64_t unlock_time) const -{ - if(unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER) - { - //interpret as block index - if(m_blockchain.size()-1 + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS >= unlock_time) - return true; - else - return false; - }else - { +bool wallet2::is_tx_spendtime_unlocked(uint64_t unlock_time) const { + if (unlock_time < m_currency.maxBlockHeight()) { + // interpret as block index + return m_blockchain.size() - 1 + m_currency.lockedTxAllowedDeltaBlocks() >= unlock_time; + } else { //interpret as time uint64_t current_time = static_cast(time(NULL)); - if(current_time + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS >= unlock_time) - return true; - else - return false; + return current_time + m_currency.lockedTxAllowedDeltaSeconds() >= unlock_time; } return false; } @@ -681,7 +821,7 @@ uint64_t wallet2::select_transfers(uint64_t needed_money, bool add_dust, uint64_ return found_money; } //---------------------------------------------------------------------------------------------------- -void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t change_amount) +void wallet2::add_unconfirmed_tx(const cryptonote::Transaction& tx, uint64_t change_amount) { unconfirmed_transfer_details& utd = m_unconfirmed_txs[cryptonote::get_transaction_hash(tx)]; utd.m_change = change_amount; @@ -690,15 +830,16 @@ void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t cha } //---------------------------------------------------------------------------------------------------- void wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, - uint64_t unlock_time, uint64_t fee, const std::vector& extra, cryptonote::transaction& tx) + uint64_t unlock_time, uint64_t fee, const std::vector& extra, cryptonote::Transaction& tx) { - transfer(dsts, fake_outputs_count, unlock_time, fee, extra, detail::digit_split_strategy, tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); + transfer(dsts, fake_outputs_count, unlock_time, fee, extra, detail::digit_split_strategy, + tx_dust_policy(m_currency.defaultDustThreshold()), tx); } //---------------------------------------------------------------------------------------------------- void wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra) { - cryptonote::transaction tx; + cryptonote::Transaction tx; transfer(dsts, fake_outputs_count, unlock_time, fee, extra, tx); } //---------------------------------------------------------------------------------------------------- diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 1447be5d..dd911b3b 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -23,9 +23,9 @@ #include #include "include_base_utils.h" -#include "cryptonote_core/account.h" #include "cryptonote_core/account_boost_serialization.h" #include "cryptonote_core/cryptonote_basic_impl.h" +#include "cryptonote_core/Currency.h" #include "net/http_client.h" #include "storages/http_abstract_invoke.h" #include "rpc/core_rpc_server_commands_defs.h" @@ -33,6 +33,7 @@ #include "common/unordered_containers_boost_serialization.h" #include "crypto/chacha8.h" #include "crypto/hash.h" +#include "common/BlockingQueue.h" #include "wallet_errors.h" @@ -44,19 +45,20 @@ namespace tools class i_wallet2_callback { public: - virtual void on_new_block(uint64_t height, const cryptonote::block& block) {} - virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, size_t out_index) {} - virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, size_t out_index, const cryptonote::transaction& spend_tx) {} - virtual void on_skip_transaction(uint64_t height, const cryptonote::transaction& tx) {} + virtual void on_new_block(uint64_t height) {} + virtual void on_money_received(uint64_t height, const cryptonote::Transaction& tx, size_t out_index) {} + virtual void on_money_spent(uint64_t height, const cryptonote::Transaction& in_tx, size_t out_index, const cryptonote::Transaction& spend_tx) {} + virtual void on_skip_transaction(uint64_t height, const cryptonote::Transaction& tx) {} }; struct tx_dust_policy { uint64_t dust_threshold; bool add_to_fee; - cryptonote::account_public_address addr_for_dust; + cryptonote::AccountPublicAddress addr_for_dust; - tx_dust_policy(uint64_t a_dust_threshold = 0, bool an_add_to_fee = true, cryptonote::account_public_address an_addr_for_dust = cryptonote::account_public_address()) + tx_dust_policy(uint64_t a_dust_threshold = 0, bool an_add_to_fee = true, + cryptonote::AccountPublicAddress an_addr_for_dust = cryptonote::AccountPublicAddress()) : dust_threshold(a_dust_threshold) , add_to_fee(an_add_to_fee) , addr_for_dust(an_addr_for_dust) @@ -64,15 +66,24 @@ namespace tools } }; - class wallet2 - { - wallet2(const wallet2&) : m_run(true), m_callback(0) {}; + class wallet2 { + wallet2(const wallet2& rhs) : + m_currency(rhs.m_currency), + m_run(true), + m_callback(0), + m_upper_transaction_size_limit(rhs.m_upper_transaction_size_limit) { + }; + public: - wallet2() : m_run(true), m_callback(0) {}; + wallet2(const cryptonote::Currency& currency) : + m_currency(currency), m_run(true), m_callback(0) { + m_upper_transaction_size_limit = (m_currency.blockGrantedFullRewardZone() * 125) / 100 - m_currency.minerTxBlobReservedSize(); + } + struct transfer_details { uint64_t m_block_height; - cryptonote::transaction m_tx; + cryptonote::Transaction m_tx; size_t m_internal_output_index; uint64_t m_global_output_index; bool m_spent; @@ -91,7 +102,7 @@ namespace tools struct unconfirmed_transfer_details { - cryptonote::transaction m_tx; + cryptonote::Transaction m_tx; uint64_t m_change; time_t m_sent_time; }; @@ -115,11 +126,13 @@ namespace tools void store(); cryptonote::account_base& get_account(){return m_account;} - void init(const std::string& daemon_address = "http://localhost:8080", uint64_t upper_transaction_size_limit = ((CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE * 125) / 100) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE); + void init(const std::string& daemon_address = "http://localhost:8080"); bool deinit(); void stop() { m_run.store(false, std::memory_order_relaxed); } + const cryptonote::Currency currency() const { return m_currency; } + i_wallet2_callback* callback() const { return m_callback; } void callback(i_wallet2_callback* callback) { m_callback = callback; } @@ -133,10 +146,11 @@ namespace tools template void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra, T destination_split_strategy, const tx_dust_policy& dust_policy); template - void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction &tx); + void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::Transaction &tx); void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra); - void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra, cryptonote::transaction& tx); + void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra, cryptonote::Transaction& tx); bool check_connection(); + bool connectClient(epee::net_utils::http::http_simple_client& client); void get_transfers(wallet2::transfer_container& incoming_transfers) const; void get_payments(const crypto::hash& payment_id, std::list& payments) const; uint64_t get_blockchain_current_height() const { return m_local_bc_height; } @@ -163,19 +177,57 @@ namespace tools private: bool store_keys(const std::string& keys_file_name, const std::string& password); void load_keys(const std::string& keys_file_name, const std::string& password); - void process_new_transaction(const cryptonote::transaction& tx, uint64_t height); - void process_new_blockchain_entry(const cryptonote::block& b, cryptonote::block_complete_entry& bche, crypto::hash& bl_id, uint64_t height); + + struct TxItem { + cryptonote::Transaction tx; + uint64_t height; + crypto::hash blockId; + crypto::public_key txPubKey; + std::vector txExtraFields; + + // resolved + std::vector outs; + uint64_t txMoneyGotInOuts; + }; + + typedef std::unique_ptr TxQueueItem; + typedef BlockingQueue TxQueue; + + bool addNewBlockchainEntry(const crypto::hash& bl_id, uint64_t start_height, uint64_t height); + + size_t processNewBlockchainEntry(TxQueue& queue, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height); + bool processNewTransaction(TxQueue& queue, const cryptonote::Transaction& tx, uint64_t height, const crypto::hash& bl_id); + void processCheckedTransaction(const TxItem& item); + + // returns number of blocks added + size_t updateBlockchain(const cryptonote::COMMAND_RPC_QUERY_BLOCKS::response& res); + void processTransactions(const cryptonote::COMMAND_RPC_QUERY_BLOCKS::response& res); + cryptonote::COMMAND_RPC_QUERY_BLOCKS::response queryBlocks(epee::net_utils::http::http_simple_client& client); + void detach_blockchain(uint64_t height); - void get_short_chain_history(std::list& ids); + void get_short_chain_history(std::list& ids) const; bool is_tx_spendtime_unlocked(uint64_t unlock_time) const; bool is_transfer_unlocked(const transfer_details& td) const; bool clear(); - void pull_blocks(size_t& blocks_added); uint64_t select_transfers(uint64_t needed_money, bool add_dust, uint64_t dust, std::list& selected_transfers); bool prepare_file_names(const std::string& file_path); - void process_unconfirmed(const cryptonote::transaction& tx); - void add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t change_amount); + void process_unconfirmed(const cryptonote::Transaction& tx); + void add_unconfirmed_tx(const cryptonote::Transaction& tx, uint64_t change_amount); + void checkGenesis(const crypto::hash& genesis_hash); //throws + inline void print_source_entry(const cryptonote::tx_source_entry& src) { + std::string indexes; + std::for_each(src.outputs.begin(), src.outputs.end(), [&](const cryptonote::tx_source_entry::output_entry& s_e) { + indexes += std::to_string(s_e.first) + " "; + }); + LOG_PRINT_L0("amount=" << m_currency.formatAmount(src.amount) << + ", real_output=" << src.real_output << + ", real_output_in_tx_index=" << src.real_output_in_tx_index << + ", indexes: " << indexes); + } + + private: + const cryptonote::Currency& m_currency; cryptonote::account_base m_account; std::string m_daemon_address; std::string m_wallet_file; @@ -188,7 +240,7 @@ namespace tools transfer_container m_transfers; payment_container m_payments; std::unordered_map m_key_images; - cryptonote::account_public_address m_account_public_address; + cryptonote::AccountPublicAddress m_account_public_address; uint64_t m_upper_transaction_size_limit; //TODO: auto-calc this value or request from daemon, now use some fixed value std::atomic m_run; @@ -288,26 +340,19 @@ namespace tools } } //---------------------------------------------------------------------------------------------------- - inline void print_source_entry(const cryptonote::tx_source_entry& src) - { - std::string indexes; - std::for_each(src.outputs.begin(), src.outputs.end(), [&](const cryptonote::tx_source_entry::output_entry& s_e) { indexes += boost::to_string(s_e.first) + " "; }); - LOG_PRINT_L0("amount=" << cryptonote::print_money(src.amount) << ", real_output=" < void wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra, T destination_split_strategy, const tx_dust_policy& dust_policy) { - cryptonote::transaction tx; + cryptonote::Transaction tx; transfer(dsts, fake_outputs_count, unlock_time, fee, extra, destination_split_strategy, dust_policy, tx); } template void wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, - uint64_t unlock_time, uint64_t fee, const std::vector& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction &tx) + uint64_t unlock_time, uint64_t fee, const std::vector& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::Transaction &tx) { using namespace cryptonote; THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination); @@ -317,7 +362,7 @@ namespace tools { THROW_WALLET_EXCEPTION_IF(0 == dt.amount, error::zero_destination); needed_money += dt.amount; - THROW_WALLET_EXCEPTION_IF(needed_money < dt.amount, error::tx_sum_overflow, dsts, fee); + THROW_WALLET_EXCEPTION_IF(needed_money < dt.amount, error::tx_sum_overflow, m_currency.publicAddressBase58Prefix(), dsts, fee); } std::list selected_transfers; @@ -393,12 +438,12 @@ namespace tools //size_t real_index = src.outputs.size() ? (rand() % src.outputs.size() ):0; tx_output_entry real_oe; real_oe.first = td.m_global_output_index; - real_oe.second = boost::get(td.m_tx.vout[td.m_internal_output_index].target).key; + real_oe.second = boost::get(td.m_tx.vout[td.m_internal_output_index].target).key; auto interted_it = src.outputs.insert(it_to_insert, real_oe); src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); src.real_output = interted_it - src.outputs.begin(); src.real_output_in_tx_index = td.m_internal_output_index; - detail::print_source_entry(src); + print_source_entry(src); ++i; } @@ -420,14 +465,14 @@ namespace tools } bool r = cryptonote::construct_tx(m_account.get_keys(), sources, splitted_dsts, extra, tx, unlock_time); - THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time); + THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, m_currency.publicAddressBase58Prefix(), sources, splitted_dsts, unlock_time); THROW_WALLET_EXCEPTION_IF(m_upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, m_upper_transaction_size_limit); std::string key_images; - bool all_are_txin_to_key = std::all_of(tx.vin.begin(), tx.vin.end(), [&](const txin_v& s_e) -> bool + bool all_are_txin_to_key = std::all_of(tx.vin.begin(), tx.vin.end(), [&](const TransactionInput& s_e) -> bool { - CHECKED_GET_SPECIFIC_VARIANT(s_e, const txin_to_key, in, false); - key_images += boost::to_string(in.k_image) + " "; + CHECKED_GET_SPECIFIC_VARIANT(s_e, const TransactionInputToKey, in, false); + key_images += boost::to_string(in.keyImage) + " "; return true; }); THROW_WALLET_EXCEPTION_IF(!all_are_txin_to_key, error::unexpected_txin_type, tx); @@ -448,9 +493,9 @@ namespace tools it->m_spent = true; LOG_PRINT_L0("Transaction successfully sent. <" << get_transaction_hash(tx) << ">" << ENDL - << "Commission: " << print_money(fee+dust) << " (dust: " << print_money(dust) << ")" << ENDL - << "Balance: " << print_money(balance()) << ENDL - << "Unlocked: " << print_money(unlocked_balance()) << ENDL + << "Commission: " << m_currency.formatAmount(fee + dust) << " (dust: " << m_currency.formatAmount(dust) << ")" << ENDL + << "Balance: " << m_currency.formatAmount(balance()) << ENDL + << "Unlocked: " << m_currency.formatAmount(unlocked_balance()) << ENDL << "Please, wait for confirmation for your balance to be unlocked."); } } diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 4ae8c9bb..47b1a581 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -140,7 +140,7 @@ namespace tools //---------------------------------------------------------------------------------------------------- struct unexpected_txin_type : public wallet_internal_error { - explicit unexpected_txin_type(std::string&& loc, const cryptonote::transaction& tx) + explicit unexpected_txin_type(std::string&& loc, const cryptonote::Transaction& tx) : wallet_internal_error(std::move(loc), "one of tx inputs has unexpected type") , m_tx(tx) { @@ -148,18 +148,17 @@ namespace tools ~unexpected_txin_type() throw() { } - const cryptonote::transaction& tx() const { return m_tx; } + const cryptonote::Transaction& tx() const { return m_tx; } std::string to_string() const { std::ostringstream ss; - cryptonote::transaction tx = m_tx; - ss << wallet_internal_error::to_string() << ", tx:\n" << cryptonote::obj_to_json_str(tx); + ss << wallet_internal_error::to_string() << ", tx:\n" << cryptonote::obj_to_json_str(m_tx); return ss.str(); } private: - cryptonote::transaction m_tx; + cryptonote::Transaction m_tx; }; //---------------------------------------------------------------------------------------------------- const char* const file_error_messages[] = { @@ -222,7 +221,7 @@ namespace tools //---------------------------------------------------------------------------------------------------- struct acc_outs_lookup_error : public refresh_error { - explicit acc_outs_lookup_error(std::string&& loc, const cryptonote::transaction& tx, + explicit acc_outs_lookup_error(std::string&& loc, const cryptonote::Transaction& tx, const crypto::public_key& tx_pub_key, const cryptonote::account_keys& acc_keys) : refresh_error(std::move(loc), "account outs lookup error") , m_tx(tx) @@ -233,20 +232,19 @@ namespace tools ~acc_outs_lookup_error() throw() { } - const cryptonote::transaction& tx() const { return m_tx; } + const cryptonote::Transaction& tx() const { return m_tx; } const crypto::public_key& tx_pub_key() const { return m_tx_pub_key; } const cryptonote::account_keys& acc_keys() const { return m_acc_keys; } std::string to_string() const { std::ostringstream ss; - cryptonote::transaction tx = m_tx; - ss << refresh_error::to_string() << ", tx: " << cryptonote::obj_to_json_str(tx); + ss << refresh_error::to_string() << ", tx: " << cryptonote::obj_to_json_str(m_tx); return ss.str(); } private: - const cryptonote::transaction m_tx; + const cryptonote::Transaction m_tx; const crypto::public_key m_tx_pub_key; const cryptonote::account_keys m_acc_keys; }; @@ -320,9 +318,9 @@ namespace tools { std::ostringstream ss; ss << transfer_error::to_string() << - ", available = " << cryptonote::print_money(m_available) << - ", tx_amount = " << cryptonote::print_money(m_tx_amount) << - ", fee = " << cryptonote::print_money(m_fee); + ", available = " << m_available << + ", tx_amount = " << m_tx_amount << + ", fee = " << m_fee; return ss.str(); } @@ -354,7 +352,7 @@ namespace tools ss << transfer_error::to_string() << ", mixin_count = " << m_mixin_count << ", scanty_outs:"; for (const auto& outs_for_amount : m_scanty_outs) { - ss << '\n' << cryptonote::print_money(outs_for_amount.amount) << " - " << outs_for_amount.outs.size(); + ss << '\n' << outs_for_amount.amount << " - " << outs_for_amount.outs.size(); } return ss.str(); } @@ -369,8 +367,10 @@ namespace tools typedef std::vector sources_t; typedef std::vector destinations_t; - explicit tx_not_constructed(std::string&& loc, const sources_t& sources, const destinations_t& destinations, uint64_t unlock_time) + explicit tx_not_constructed(std::string&& loc, uint64_t addressPrefix, const sources_t& sources, + const destinations_t& destinations, uint64_t unlock_time) : transfer_error(std::move(loc), "transaction was not constructed") + , m_addressPrefix(addressPrefix) , m_sources(sources) , m_destinations(destinations) , m_unlock_time(unlock_time) @@ -392,7 +392,7 @@ namespace tools { const cryptonote::tx_source_entry& src = m_sources[i]; ss << "\n source " << i << ":"; - ss << "\n amount: " << cryptonote::print_money(src.amount); + ss << "\n amount: " << src.amount; // It's not good, if logs will contain such much data //ss << "\n real_output: " << src.real_output; //ss << "\n real_output_in_tx_index: " << src.real_output_in_tx_index; @@ -409,8 +409,7 @@ namespace tools for (size_t i = 0; i < m_destinations.size(); ++i) { const cryptonote::tx_destination_entry& dst = m_destinations[i]; - ss << "\n " << i << ": " << cryptonote::get_account_address_as_str(dst.addr) << " " << - cryptonote::print_money(dst.amount); + ss << "\n " << i << ": " << getAccountAddressAsStr(m_addressPrefix, dst.addr) << ' ' << dst.amount; } ss << "\nunlock_time: " << m_unlock_time; @@ -419,6 +418,7 @@ namespace tools } private: + uint64_t m_addressPrefix; sources_t m_sources; destinations_t m_destinations; uint64_t m_unlock_time; @@ -426,7 +426,7 @@ namespace tools //---------------------------------------------------------------------------------------------------- struct tx_rejected : public transfer_error { - explicit tx_rejected(std::string&& loc, const cryptonote::transaction& tx, const std::string& status) + explicit tx_rejected(std::string&& loc, const cryptonote::Transaction& tx, const std::string& status) : transfer_error(std::move(loc), "transaction was rejected by daemon") , m_tx(tx) , m_status(status) @@ -435,27 +435,29 @@ namespace tools ~tx_rejected() throw() { } - const cryptonote::transaction& tx() const { return m_tx; } + const cryptonote::Transaction& tx() const { return m_tx; } const std::string& status() const { return m_status; } std::string to_string() const { std::ostringstream ss; ss << transfer_error::to_string() << ", status = " << m_status << ", tx:\n"; - cryptonote::transaction tx = m_tx; - ss << cryptonote::obj_to_json_str(tx); + ss << cryptonote::obj_to_json_str(m_tx); return ss.str(); } private: - cryptonote::transaction m_tx; + cryptonote::Transaction m_tx; std::string m_status; }; //---------------------------------------------------------------------------------------------------- struct tx_sum_overflow : public transfer_error { - explicit tx_sum_overflow(std::string&& loc, const std::vector& destinations, uint64_t fee) - : transfer_error(std::move(loc), "transaction sum + fee exceeds " + cryptonote::print_money(std::numeric_limits::max())) + typedef std::vector destinations_t; + + explicit tx_sum_overflow(std::string&& loc, uint64_t addressPrefix, const destinations_t& destinations, uint64_t fee) + : transfer_error(std::move(loc), "transaction sum + fee exceeds " + std::to_string(std::numeric_limits::max())) + , m_addressPrefix(addressPrefix) , m_destinations(destinations) , m_fee(fee) { @@ -470,23 +472,24 @@ namespace tools { std::ostringstream ss; ss << transfer_error::to_string() << - ", fee = " << cryptonote::print_money(m_fee) << + ", fee = " << m_fee << ", destinations:"; for (const auto& dst : m_destinations) { - ss << '\n' << cryptonote::print_money(dst.amount) << " -> " << cryptonote::get_account_address_as_str(dst.addr); + ss << '\n' << dst.amount << " -> " << getAccountAddressAsStr(m_addressPrefix, dst.addr); } return ss.str(); } private: - std::vector m_destinations; + uint64_t m_addressPrefix; + destinations_t m_destinations; uint64_t m_fee; }; //---------------------------------------------------------------------------------------------------- struct tx_too_big : public transfer_error { - explicit tx_too_big(std::string&& loc, const cryptonote::transaction& tx, uint64_t tx_size_limit) + explicit tx_too_big(std::string&& loc, const cryptonote::Transaction& tx, uint64_t tx_size_limit) : transfer_error(std::move(loc), "transaction is too big") , m_tx(tx) , m_tx_size_limit(tx_size_limit) @@ -495,22 +498,21 @@ namespace tools ~tx_too_big() throw() { } - const cryptonote::transaction& tx() const { return m_tx; } + const cryptonote::Transaction& tx() const { return m_tx; } uint64_t tx_size_limit() const { return m_tx_size_limit; } std::string to_string() const { std::ostringstream ss; - cryptonote::transaction tx = m_tx; ss << transfer_error::to_string() << ", tx_size_limit = " << m_tx_size_limit << ", tx size = " << get_object_blobsize(m_tx) << - ", tx:\n" << cryptonote::obj_to_json_str(tx); + ", tx:\n" << cryptonote::obj_to_json_str(m_tx); return ss.str(); } private: - cryptonote::transaction m_tx; + cryptonote::Transaction m_tx; uint64_t m_tx_size_limit; }; //---------------------------------------------------------------------------------------------------- diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index ea446a4e..de783cbb 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -44,7 +44,11 @@ namespace tools bool wallet_rpc_server::run() { m_net_server.add_idle_handler([this](){ - m_wallet.refresh(); + try { + m_wallet.refresh(); + } catch (const std::exception& ex) { + LOG_ERROR("Exception at while refreshing, what=" << ex.what()); + } return true; }, 20000); @@ -88,7 +92,7 @@ namespace tools std::vector dsts; for (auto it = req.destinations.begin(); it != req.destinations.end(); it++) { cryptonote::tx_destination_entry de; - if (!get_account_address_from_str(de.addr, it->address)) { + if (!m_wallet.currency().parseAccountAddressString(it->address, de.addr)) { er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; er.message = std::string("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS: ") + it->address; return false; @@ -118,7 +122,7 @@ namespace tools } try { - cryptonote::transaction tx; + cryptonote::Transaction tx; m_wallet.transfer(dsts, req.mixin, req.unlock_time, req.fee, extra, tx); res.tx_hash = boost::lexical_cast(cryptonote::get_transaction_hash(tx)); return true; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 35beea14..de7ae5ff 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,6 +8,7 @@ file(GLOB_RECURSE CRYPTO_TESTS crypto/*) file(GLOB_RECURSE FUNC_TESTS functional_tests/*) file(GLOB_RECURSE PERFORMANCE_TESTS performance_tests/*) file(GLOB_RECURSE CORE_PROXY core_proxy/*) +file(GLOB_RECURSE TEST_GENERATOR TestGenerator/*) file(GLOB_RECURSE UNIT_TESTS unit_tests/*) source_group(core_tests FILES ${CORE_TESTS}) @@ -15,11 +16,12 @@ source_group(crypto_tests FILES ${CRYPTO_TESTS}) source_group(functional_tests FILES ${FUNC_TESTS}) source_group(performance_tests FILES ${PERFORMANCE_TESTS}) source_group(core_proxy FILES ${CORE_PROXY}) +source_group(TestGenerator FILES ${TEST_GENERATOR}) source_group(unit_tests FILES ${UNIT_TESTS}) # add_subdirectory(daemon_tests) -add_library(coretests_lib ${CORE_TESTS}) +add_library(TestGenerator ${TEST_GENERATOR}) add_executable(coretests ${CORE_TESTS}) add_executable(crypto-tests ${CRYPTO_TESTS}) @@ -33,31 +35,30 @@ add_executable(unit_tests ${UNIT_TESTS}) add_executable(net_load_tests_clt net_load_tests/clt.cpp) add_executable(net_load_tests_srv net_load_tests/srv.cpp) -target_link_libraries(core_proxy cryptonote_core common crypto upnpc-static ${Boost_LIBRARIES}) -target_link_libraries(coretests cryptonote_core common crypto ${Boost_LIBRARIES}) -target_link_libraries(difficulty-tests cryptonote_core) -target_link_libraries(functional_tests cryptonote_core wallet common crypto ${Boost_LIBRARIES}) +target_link_libraries(core_proxy epee cryptonote_core common crypto upnpc-static ${Boost_LIBRARIES}) +target_link_libraries(coretests epee cryptonote_core common crypto TestGenerator ${Boost_LIBRARIES}) +target_link_libraries(difficulty-tests epee common crypto cryptonote_core ${Boost_LIBRARIES}) +target_link_libraries(functional_tests epee cryptonote_core wallet common crypto ${Boost_LIBRARIES}) target_link_libraries(hash-tests crypto) -target_link_libraries(hash-target-tests crypto cryptonote_core) -target_link_libraries(performance_tests cryptonote_core common crypto ${Boost_LIBRARIES}) -target_link_libraries(unit_tests cryptonote_core common crypto wallet coretests_lib gtest_main ${Boost_LIBRARIES}) -target_link_libraries(net_load_tests_clt cryptonote_core common crypto gtest_main ${Boost_LIBRARIES}) -target_link_libraries(net_load_tests_srv cryptonote_core common crypto gtest_main ${Boost_LIBRARIES}) - - +target_link_libraries(hash-target-tests epee crypto cryptonote_core) +target_link_libraries(performance_tests epee cryptonote_core common crypto ${Boost_LIBRARIES}) +target_link_libraries(unit_tests epee wallet TestGenerator cryptonote_core common crypto gtest_main ${Boost_LIBRARIES}) +target_link_libraries(net_load_tests_clt epee cryptonote_core common crypto gtest_main ${Boost_LIBRARIES}) +target_link_libraries(net_load_tests_srv epee cryptonote_core common crypto gtest_main ${Boost_LIBRARIES}) file(GLOB_RECURSE NODE_RPC_PROXY_TEST node_rpc_proxy_test/*) source_group(node_rpc_proxy_test FILES ${NODE_RPC_PROXY_TEST}) add_executable(node_rpc_proxy_test ${NODE_RPC_PROXY_TEST}) -target_link_libraries(node_rpc_proxy_test node_rpc_proxy cryptonote_core common crypto ${Boost_LIBRARIES}) - +target_link_libraries(node_rpc_proxy_test epee node_rpc_proxy cryptonote_core common crypto ${Boost_LIBRARIES}) if(NOT MSVC) - set_property(TARGET gtest gtest_main unit_tests net_load_tests_clt net_load_tests_srv APPEND_STRING PROPERTY COMPILE_FLAGS " -Wno-undef -Wno-sign-compare") + set_property(TARGET gtest gtest_main unit_tests net_load_tests_clt net_load_tests_srv TestGenerator APPEND_STRING PROPERTY COMPILE_FLAGS " -Wno-undef -Wno-sign-compare") endif() add_custom_target(tests DEPENDS coretests difficulty hash performance_tests core_proxy unit_tests node_rpc_proxy_test) -set_property(TARGET coretests crypto-tests functional_tests difficulty-tests gtest gtest_main hash-tests hash-target-tests performance_tests core_proxy unit_tests tests net_load_tests_clt net_load_tests_srv node_rpc_proxy_test PROPERTY FOLDER "tests") +set_property(TARGET coretests crypto-tests functional_tests difficulty-tests gtest gtest_main hash-tests hash-target-tests performance_tests core_proxy unit_tests tests net_load_tests_clt net_load_tests_srv node_rpc_proxy_test TestGenerator PROPERTY FOLDER "tests") + +add_dependencies(core_proxy version) add_test(coretests coretests --generate_and_play_test_data) add_test(crypto crypto-tests ${CMAKE_CURRENT_SOURCE_DIR}/crypto/tests.txt) diff --git a/tests/TestGenerator/TestGenerator.cpp b/tests/TestGenerator/TestGenerator.cpp new file mode 100644 index 00000000..2fd6818e --- /dev/null +++ b/tests/TestGenerator/TestGenerator.cpp @@ -0,0 +1,367 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "TestGenerator.h" + +// epee +#include "misc_language.h" + +#include "cryptonote_core/account.h" +#include "cryptonote_core/miner.h" + +using namespace std; + +using namespace epee; +using namespace cryptonote; + + +void test_generator::getBlockchain(std::vector& blockchain, const crypto::hash& head, size_t n) const { + crypto::hash curr = head; + while (null_hash != curr && blockchain.size() < n) { + auto it = m_blocksInfo.find(curr); + if (m_blocksInfo.end() == it) { + throw std::runtime_error("block hash wasn't found"); + } + + blockchain.push_back(it->second); + curr = it->second.prevId; + } + + std::reverse(blockchain.begin(), blockchain.end()); +} + +void test_generator::getLastNBlockSizes(std::vector& blockSizes, const crypto::hash& head, size_t n) const { + std::vector blockchain; + getBlockchain(blockchain, head, n); + for (auto& bi : blockchain) { + blockSizes.push_back(bi.blockSize); + } +} + +uint64_t test_generator::getAlreadyGeneratedCoins(const crypto::hash& blockId) const { + auto it = m_blocksInfo.find(blockId); + if (it == m_blocksInfo.end()) { + throw std::runtime_error("block hash wasn't found"); + } + + return it->second.alreadyGeneratedCoins; +} + +uint64_t test_generator::getAlreadyGeneratedCoins(const cryptonote::Block& blk) const { + crypto::hash blkHash; + get_block_hash(blk, blkHash); + return getAlreadyGeneratedCoins(blkHash); +} + +void test_generator::addBlock(const cryptonote::Block& blk, size_t tsxSize, uint64_t fee, + std::vector& blockSizes, uint64_t alreadyGeneratedCoins) { + const size_t blockSize = tsxSize + get_object_blobsize(blk.minerTx); + int64_t emissionChange; + uint64_t blockReward; + bool penalizeFee = blk.majorVersion > BLOCK_MAJOR_VERSION_1; + m_currency.getBlockReward(misc_utils::median(blockSizes), blockSize, alreadyGeneratedCoins, fee, penalizeFee, + blockReward, emissionChange); + m_blocksInfo[get_block_hash(blk)] = BlockInfo(blk.prevId, alreadyGeneratedCoins + emissionChange, blockSize); +} + +bool test_generator::constructBlock(cryptonote::Block& blk, uint64_t height, const crypto::hash& prevId, + const cryptonote::account_base& minerAcc, uint64_t timestamp, uint64_t alreadyGeneratedCoins, + std::vector& blockSizes, const std::list& txList) { + blk.majorVersion = defaultMajorVersion; + blk.minorVersion = defaultMinorVersion; + blk.timestamp = timestamp; + blk.prevId = prevId; + + blk.txHashes.reserve(txList.size()); + for (const Transaction &tx : txList) { + crypto::hash tx_hash; + get_transaction_hash(tx, tx_hash); + blk.txHashes.push_back(tx_hash); + } + + uint64_t totalFee = 0; + size_t txsSize = 0; + for (auto& tx : txList) { + uint64_t fee = 0; + bool r = get_tx_fee(tx, fee); + CHECK_AND_ASSERT_MES(r, false, "wrong transaction passed to construct_block"); + totalFee += fee; + txsSize += get_object_blobsize(tx); + } + + blk.minerTx = AUTO_VAL_INIT(blk.minerTx); + size_t targetBlockSize = txsSize + get_object_blobsize(blk.minerTx); + while (true) { + if (!m_currency.constructMinerTx(height, misc_utils::median(blockSizes), alreadyGeneratedCoins, targetBlockSize, + totalFee, minerAcc.get_keys().m_account_address, blk.minerTx, blobdata(), 10)) { + return false; + } + + size_t actualBlockSize = txsSize + get_object_blobsize(blk.minerTx); + if (targetBlockSize < actualBlockSize) { + targetBlockSize = actualBlockSize; + } else if (actualBlockSize < targetBlockSize) { + size_t delta = targetBlockSize - actualBlockSize; + blk.minerTx.extra.resize(blk.minerTx.extra.size() + delta, 0); + actualBlockSize = txsSize + get_object_blobsize(blk.minerTx); + if (actualBlockSize == targetBlockSize) { + break; + } else { + CHECK_AND_ASSERT_MES(targetBlockSize < actualBlockSize, false, "Unexpected block size"); + delta = actualBlockSize - targetBlockSize; + blk.minerTx.extra.resize(blk.minerTx.extra.size() - delta); + actualBlockSize = txsSize + get_object_blobsize(blk.minerTx); + if (actualBlockSize == targetBlockSize) { + break; + } else { + CHECK_AND_ASSERT_MES(actualBlockSize < targetBlockSize, false, "Unexpected block size"); + blk.minerTx.extra.resize(blk.minerTx.extra.size() + delta, 0); + targetBlockSize = txsSize + get_object_blobsize(blk.minerTx); + } + } + } else { + break; + } + } + + if (blk.majorVersion >= BLOCK_MAJOR_VERSION_2) { + blk.parentBlock.majorVersion = BLOCK_MAJOR_VERSION_1; + blk.parentBlock.minorVersion = BLOCK_MINOR_VERSION_0; + blk.parentBlock.numberOfTransactions = 1; + + cryptonote::tx_extra_merge_mining_tag mmTag; + mmTag.depth = 0; + if (!cryptonote::get_aux_block_header_hash(blk, mmTag.merkle_root)) { + return false; + } + + blk.parentBlock.minerTx.extra.clear(); + if (!cryptonote::append_mm_tag_to_extra(blk.parentBlock.minerTx.extra, mmTag)) { + return false; + } + } + + // Nonce search... + blk.nonce = 0; + crypto::cn_context context; + while (!miner::find_nonce_for_given_block(context, blk, getTestDifficulty())) { + blk.timestamp++; + } + + addBlock(blk, txsSize, totalFee, blockSizes, alreadyGeneratedCoins); + + return true; +} + +bool test_generator::constructBlock(cryptonote::Block& blk, const cryptonote::account_base& minerAcc, uint64_t timestamp) { + std::vector blockSizes; + std::list txList; + return constructBlock(blk, 0, null_hash, minerAcc, timestamp, 0, blockSizes, txList); +} + +bool test_generator::constructBlock(cryptonote::Block& blk, const cryptonote::Block& blkPrev, + const cryptonote::account_base& minerAcc, + const std::list& txList/* = std::list()*/) { + uint64_t height = boost::get(blkPrev.minerTx.vin.front()).height + 1; + crypto::hash prevId = get_block_hash(blkPrev); + // Keep difficulty unchanged + uint64_t timestamp = blkPrev.timestamp + m_currency.difficultyTarget(); + uint64_t alreadyGeneratedCoins = getAlreadyGeneratedCoins(prevId); + std::vector blockSizes; + getLastNBlockSizes(blockSizes, prevId, m_currency.rewardBlocksWindow()); + + return constructBlock(blk, height, prevId, minerAcc, timestamp, alreadyGeneratedCoins, blockSizes, txList); +} + +bool test_generator::constructBlockManually(Block& blk, const Block& prevBlock, const account_base& minerAcc, + int actualParams/* = bf_none*/, uint8_t majorVer/* = 0*/, + uint8_t minorVer/* = 0*/, uint64_t timestamp/* = 0*/, + const crypto::hash& prevId/* = crypto::hash()*/, const difficulty_type& diffic/* = 1*/, + const Transaction& minerTx/* = transaction()*/, + const std::vector& txHashes/* = std::vector()*/, + size_t txsSizes/* = 0*/, uint64_t fee/* = 0*/) { + blk.majorVersion = actualParams & bf_major_ver ? majorVer : defaultMajorVersion; + blk.minorVersion = actualParams & bf_minor_ver ? minorVer : defaultMinorVersion; + blk.timestamp = actualParams & bf_timestamp ? timestamp : prevBlock.timestamp + m_currency.difficultyTarget(); // Keep difficulty unchanged + blk.prevId = actualParams & bf_prev_id ? prevId : get_block_hash(prevBlock); + blk.txHashes = actualParams & bf_tx_hashes ? txHashes : std::vector(); + + size_t height = get_block_height(prevBlock) + 1; + uint64_t alreadyGeneratedCoins = getAlreadyGeneratedCoins(prevBlock); + std::vector blockSizes; + getLastNBlockSizes(blockSizes, get_block_hash(prevBlock), m_currency.rewardBlocksWindow()); + if (actualParams & bf_miner_tx) { + blk.minerTx = minerTx; + } else { + size_t currentBlockSize = txsSizes + get_object_blobsize(blk.minerTx); + // TODO: This will work, until size of constructed block is less then m_currency.blockGrantedFullRewardZone() + if (!m_currency.constructMinerTx(height, misc_utils::median(blockSizes), alreadyGeneratedCoins, currentBlockSize, 0, + minerAcc.get_keys().m_account_address, blk.minerTx, blobdata(), 1, blk.majorVersion > BLOCK_MAJOR_VERSION_1)) { + return false; + } + } + + if (blk.majorVersion >= BLOCK_MAJOR_VERSION_2) { + blk.parentBlock.majorVersion = BLOCK_MAJOR_VERSION_1; + blk.parentBlock.minorVersion = BLOCK_MINOR_VERSION_0; + blk.parentBlock.numberOfTransactions = 1; + + cryptonote::tx_extra_merge_mining_tag mmTag; + mmTag.depth = 0; + if (!cryptonote::get_aux_block_header_hash(blk, mmTag.merkle_root)) { + return false; + } + + blk.parentBlock.minerTx.extra.clear(); + if (!cryptonote::append_mm_tag_to_extra(blk.parentBlock.minerTx.extra, mmTag)) { + return false; + } + } + + difficulty_type aDiffic = actualParams & bf_diffic ? diffic : getTestDifficulty(); + if (1 < aDiffic) { + fillNonce(blk, aDiffic); + } + + addBlock(blk, txsSizes, fee, blockSizes, alreadyGeneratedCoins); + + return true; +} + +bool test_generator::constructBlockManuallyTx(cryptonote::Block& blk, const cryptonote::Block& prevBlock, + const cryptonote::account_base& minerAcc, + const std::vector& txHashes, size_t txsSize) { + return constructBlockManually(blk, prevBlock, minerAcc, bf_tx_hashes, 0, 0, 0, crypto::hash(), 0, Transaction(), + txHashes, txsSize); +} + +bool test_generator::constructMaxSizeBlock(cryptonote::Block& blk, const cryptonote::Block& blkPrev, + const cryptonote::account_base& minerAccount, + size_t medianBlockCount/* = 0*/, + const std::list& txList/* = std::list()*/) { + std::vector blockSizes; + medianBlockCount = medianBlockCount == 0 ? m_currency.rewardBlocksWindow() : medianBlockCount; + getLastNBlockSizes(blockSizes, get_block_hash(blkPrev), medianBlockCount); + + size_t median = misc_utils::median(blockSizes); + median = std::max(median, m_currency.blockGrantedFullRewardZone()); + + uint64_t totalFee = 0; + size_t txsSize = 0; + std::vector txHashes; + for (auto& tx : txList) { + uint64_t fee = 0; + bool r = get_tx_fee(tx, fee); + CHECK_AND_ASSERT_MES(r, false, "wrong transaction passed to construct_max_size_block"); + totalFee += fee; + txsSize += get_object_blobsize(tx); + txHashes.push_back(get_transaction_hash(tx)); + } + + Transaction minerTx; + bool r = constructMinerTxBySize(m_currency, minerTx, get_block_height(blkPrev) + 1, + getAlreadyGeneratedCoins(blkPrev), minerAccount.get_keys().m_account_address, blockSizes, + 2 * median - txsSize, 2 * median, totalFee, defaultMajorVersion > BLOCK_MAJOR_VERSION_1); + if (!r) { + return false; + } + + return constructBlockManually(blk, blkPrev, minerAccount, test_generator::bf_miner_tx | test_generator::bf_tx_hashes, + 0, 0, 0, crypto::hash(), 0, minerTx, txHashes, txsSize, totalFee); +} + +void fillNonce(cryptonote::Block& blk, const difficulty_type& diffic) { + blk.nonce = 0; + crypto::cn_context context; + while (!miner::find_nonce_for_given_block(context, blk, diffic)) { + blk.timestamp++; + } +} + +bool constructMinerTxManually(const cryptonote::Currency& currency, size_t height, uint64_t alreadyGeneratedCoins, + const AccountPublicAddress& minerAddress, Transaction& tx, uint64_t fee, + KeyPair* pTxKey/* = 0*/) { + KeyPair txkey; + txkey = KeyPair::generate(); + add_tx_pub_key_to_extra(tx, txkey.pub); + + if (0 != pTxKey) { + *pTxKey = txkey; + } + + TransactionInputGenerate in; + in.height = height; + tx.vin.push_back(in); + + // This will work, until size of constructed block is less then currency.blockGrantedFullRewardZone() + int64_t emissionChange; + uint64_t blockReward; + if (!currency.getBlockReward(0, 0, alreadyGeneratedCoins, fee, false, blockReward, emissionChange)) { + LOG_PRINT_L0("Block is too big"); + return false; + } + + crypto::key_derivation derivation; + crypto::public_key outEphPublicKey; + crypto::generate_key_derivation(minerAddress.m_viewPublicKey, txkey.sec, derivation); + crypto::derive_public_key(derivation, 0, minerAddress.m_spendPublicKey, outEphPublicKey); + + TransactionOutput out; + out.amount = blockReward; + out.target = TransactionOutputToKey(outEphPublicKey); + tx.vout.push_back(out); + + tx.version = CURRENT_TRANSACTION_VERSION; + tx.unlockTime = height + currency.minedMoneyUnlockWindow(); + + return true; +} + +bool constructMinerTxBySize(const cryptonote::Currency& currency, cryptonote::Transaction& minerTx, uint64_t height, + uint64_t alreadyGeneratedCoins, const cryptonote::AccountPublicAddress& minerAddress, + std::vector& blockSizes, size_t targetTxSize, size_t targetBlockSize, + uint64_t fee/* = 0*/, bool penalizeFee/* = false*/) { + if (!currency.constructMinerTx(height, misc_utils::median(blockSizes), alreadyGeneratedCoins, targetBlockSize, + fee, minerAddress, minerTx, cryptonote::blobdata(), 1, penalizeFee)) { + return false; + } + + size_t currentSize = get_object_blobsize(minerTx); + size_t tryCount = 0; + while (targetTxSize != currentSize) { + ++tryCount; + if (10 < tryCount) { + return false; + } + + if (targetTxSize < currentSize) { + size_t diff = currentSize - targetTxSize; + if (diff <= minerTx.extra.size()) { + minerTx.extra.resize(minerTx.extra.size() - diff); + } else { + return false; + } + } else { + size_t diff = targetTxSize - currentSize; + minerTx.extra.resize(minerTx.extra.size() + diff); + } + + currentSize = get_object_blobsize(minerTx); + } + + return true; +} diff --git a/tests/TestGenerator/TestGenerator.h b/tests/TestGenerator/TestGenerator.h new file mode 100644 index 00000000..e7b46585 --- /dev/null +++ b/tests/TestGenerator/TestGenerator.h @@ -0,0 +1,115 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include +#include +#include +#include + +#include "crypto/hash.h" +#include "cryptonote_core/cryptonote_basic.h" +#include "cryptonote_core/cryptonote_basic_impl.h" +#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_core/Currency.h" +#include "cryptonote_core/difficulty.h" + + +class test_generator +{ +public: + struct BlockInfo { + BlockInfo() + : prevId() + , alreadyGeneratedCoins(0) + , blockSize(0) { + } + + BlockInfo(crypto::hash aPrevId, uint64_t anAlreadyGeneratedCoins, size_t aBlockSize) + : prevId(aPrevId) + , alreadyGeneratedCoins(anAlreadyGeneratedCoins) + , blockSize(aBlockSize) { + } + + crypto::hash prevId; + uint64_t alreadyGeneratedCoins; + size_t blockSize; + }; + + enum BlockFields { + bf_none = 0, + bf_major_ver = 1 << 0, + bf_minor_ver = 1 << 1, + bf_timestamp = 1 << 2, + bf_prev_id = 1 << 3, + bf_miner_tx = 1 << 4, + bf_tx_hashes = 1 << 5, + bf_diffic = 1 << 6 + }; + + test_generator(const cryptonote::Currency& currency, uint8_t majorVersion = cryptonote::BLOCK_MAJOR_VERSION_1, + uint8_t minorVersion = cryptonote::BLOCK_MINOR_VERSION_0) + : m_currency(currency), defaultMajorVersion(majorVersion), defaultMinorVersion(minorVersion) { + } + + + uint8_t defaultMajorVersion; + uint8_t defaultMinorVersion; + + const cryptonote::Currency& currency() const { return m_currency; } + + void getBlockchain(std::vector& blockchain, const crypto::hash& head, size_t n) const; + void getLastNBlockSizes(std::vector& blockSizes, const crypto::hash& head, size_t n) const; + uint64_t getAlreadyGeneratedCoins(const crypto::hash& blockId) const; + uint64_t getAlreadyGeneratedCoins(const cryptonote::Block& blk) const; + + void addBlock(const cryptonote::Block& blk, size_t tsxSize, uint64_t fee, std::vector& blockSizes, + uint64_t alreadyGeneratedCoins); + bool constructBlock(cryptonote::Block& blk, uint64_t height, const crypto::hash& prevId, + const cryptonote::account_base& minerAcc, uint64_t timestamp, uint64_t alreadyGeneratedCoins, + std::vector& blockSizes, const std::list& txList); + bool constructBlock(cryptonote::Block& blk, const cryptonote::account_base& minerAcc, uint64_t timestamp); + bool constructBlock(cryptonote::Block& blk, const cryptonote::Block& blkPrev, const cryptonote::account_base& minerAcc, + const std::list& txList = std::list()); + + bool constructBlockManually(cryptonote::Block& blk, const cryptonote::Block& prevBlock, + const cryptonote::account_base& minerAcc, int actualParams = bf_none, uint8_t majorVer = 0, + uint8_t minorVer = 0, uint64_t timestamp = 0, const crypto::hash& prevId = crypto::hash(), + const cryptonote::difficulty_type& diffic = 1, const cryptonote::Transaction& minerTx = cryptonote::Transaction(), + const std::vector& txHashes = std::vector(), size_t txsSizes = 0, uint64_t fee = 0); + bool constructBlockManuallyTx(cryptonote::Block& blk, const cryptonote::Block& prevBlock, + const cryptonote::account_base& minerAcc, const std::vector& txHashes, size_t txsSize); + bool constructMaxSizeBlock(cryptonote::Block& blk, const cryptonote::Block& blkPrev, + const cryptonote::account_base& minerAccount, size_t medianBlockCount = 0, + const std::list& txList = std::list()); + +private: + const cryptonote::Currency& m_currency; + std::unordered_map m_blocksInfo; +}; + +inline cryptonote::difficulty_type getTestDifficulty() { return 1; } +void fillNonce(cryptonote::Block& blk, const cryptonote::difficulty_type& diffic); + +bool constructMinerTxManually(const cryptonote::Currency& currency, size_t height, uint64_t alreadyGeneratedCoins, + const cryptonote::AccountPublicAddress& minerAddress, cryptonote::Transaction& tx, uint64_t fee, + cryptonote::KeyPair* pTxKey = 0); +bool constructMinerTxBySize(const cryptonote::Currency& currency, cryptonote::Transaction& minerTx, uint64_t height, + uint64_t alreadyGeneratedCoins, const cryptonote::AccountPublicAddress& minerAddress, + std::vector& blockSizes, size_t targetTxSize, size_t targetBlockSize, uint64_t fee = 0, + bool penalizeFee = false); diff --git a/tests/core_proxy/core_proxy.cpp b/tests/core_proxy/core_proxy.cpp index 8a179a94..a2db0ca7 100644 --- a/tests/core_proxy/core_proxy.cpp +++ b/tests/core_proxy/core_proxy.cpp @@ -33,7 +33,6 @@ using namespace std; #include "common/command_line.h" #include "console_handler.h" #include "p2p/net_node.h" -//#include "cryptonote_core/cryptonote_core.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" #include "core_proxy.h" #include "version.h" @@ -89,7 +88,8 @@ int main(int argc, char* argv[]) //create objects and link them - tests::proxy_core pr_core; + cryptonote::Currency currency = cryptonote::CurrencyBuilder().currency(); + tests::proxy_core pr_core(currency); cryptonote::t_cryptonote_protocol_handler cprotocol(pr_core, NULL); nodetool::node_server > p2psrv(cprotocol); cprotocol.set_p2p_endpoint(&p2psrv); @@ -99,7 +99,7 @@ int main(int argc, char* argv[]) //initialize objects LOG_PRINT_L0("Initializing p2p server..."); - bool res = p2psrv.init(vm); + bool res = p2psrv.init(vm, false); CHECK_AND_ASSERT_MES(res, 1, "Failed to initialize p2p server."); LOG_PRINT_L0("P2p server initialized OK"); @@ -143,7 +143,7 @@ string tx2str(const cryptonote::transaction& tx, const cryptonote::hash256& tx_h ss << "{" << endl; ss << "\tversion:" << tx.version << endl; - ss << "\tunlock_time:" << tx.unlock_time << endl; + ss << "\tunlock_time:" << tx.unlockTime << endl; ss << "\t" return ss.str(); @@ -155,7 +155,7 @@ bool tests::proxy_core::handle_incoming_tx(const cryptonote::blobdata& tx_blob, crypto::hash tx_hash = null_hash; crypto::hash tx_prefix_hash = null_hash; - transaction tx; + Transaction tx; if (!parse_and_validate_tx_from_blob(tx_blob, tx, tx_hash, tx_prefix_hash)) { cerr << "WRONG TRANSACTION BLOB, Failed to parse, rejected" << endl; @@ -173,30 +173,30 @@ bool tests::proxy_core::handle_incoming_tx(const cryptonote::blobdata& tx_blob, return true; } -bool tests::proxy_core::handle_incoming_block(const cryptonote::blobdata& block_blob, cryptonote::block_verification_context& bvc, bool update_miner_blocktemplate) { - block b = AUTO_VAL_INIT(b); +bool tests::proxy_core::handle_incoming_block_blob(const cryptonote::blobdata& block_blob, cryptonote::block_verification_context& bvc, bool control_miner, bool relay_block) { + Block b = AUTO_VAL_INIT(b); - if(!parse_and_validate_block_from_blob(block_blob, b)) { - cerr << "Failed to parse and validate new block" << endl; - return false; - } + if (!parse_and_validate_block_from_blob(block_blob, b)) { + cerr << "Failed to parse and validate new block" << endl; + return false; + } - crypto::hash h; - crypto::hash lh; - cout << "BLOCK" << endl << endl; - cout << (h = get_block_hash(b)) << endl; - cout << (lh = get_block_longhash(m_cn_context, b, 0)) << endl; - cout << get_transaction_hash(b.miner_tx) << endl; - cout << ::get_object_blobsize(b.miner_tx) << endl; - //cout << string_tools::buff_to_hex_nodelimer(block_blob) << endl; - cout << obj_to_json_str(b) << endl; + crypto::hash h; + crypto::hash lh; + if (!get_block_longhash(m_cn_context, b, lh)) { + return false; + } - cout << endl << "ENDBLOCK" << endl << endl; + cout << "BLOCK" << endl << endl; + cout << (h = get_block_hash(b)) << endl; + cout << lh << endl; + cout << get_transaction_hash(b.minerTx) << endl; + cout << ::get_object_blobsize(b.minerTx) << endl; + //cout << string_tools::buff_to_hex_nodelimer(block_blob) << endl; + cout << obj_to_json_str(b) << endl; + cout << endl << "ENDBLOCK" << endl << endl; - if (!add_block(h, lh, b, block_blob)) - return false; - - return true; + return add_block(h, lh, b, block_to_blob(b)); } bool tests::proxy_core::get_short_chain_history(std::list& ids) { @@ -211,9 +211,13 @@ bool tests::proxy_core::get_blockchain_top(uint64_t& height, crypto::hash& top_i } bool tests::proxy_core::init(const boost::program_options::variables_map& /*vm*/) { - generate_genesis_block(m_genesis); - crypto::hash h = get_block_hash(m_genesis); - add_block(h, get_block_longhash(m_cn_context, m_genesis, 0), m_genesis, block_to_blob(m_genesis)); + m_genesis = m_currency.genesisBlock(); + crypto::hash h = m_currency.genesisBlockHash(); + crypto::hash lh; + if (!get_block_longhash(m_cn_context, m_genesis, lh)) { + return false; + } + add_block(h, lh, m_genesis, block_to_blob(m_genesis)); return true; } @@ -231,20 +235,20 @@ void tests::proxy_core::build_short_history(std::list &m_history, m_history.push_front(cit->first); size_t n = 1 << m_history.size(); - while (m_hash2blkidx.end() != cit && cryptonote::null_hash != cit->second.blk.prev_id && n > 0) { + while (m_hash2blkidx.end() != cit && cryptonote::null_hash != cit->second.blk.prevId && n > 0) { n--; - cit = m_hash2blkidx.find(cit->second.blk.prev_id); + cit = m_hash2blkidx.find(cit->second.blk.prevId); } } while (m_hash2blkidx.end() != cit && get_block_hash(cit->second.blk) != cit->first);*/ } -bool tests::proxy_core::add_block(const crypto::hash &_id, const crypto::hash &_longhash, const cryptonote::block &_blk, const cryptonote::blobdata &_blob) { +bool tests::proxy_core::add_block(const crypto::hash &_id, const crypto::hash &_longhash, const cryptonote::Block &_blk, const cryptonote::blobdata &_blob) { size_t height = 0; - if (cryptonote::null_hash != _blk.prev_id) { - std::unordered_map::const_iterator cit = m_hash2blkidx.find(_blk.prev_id); + if (cryptonote::null_hash != _blk.prevId) { + std::unordered_map::const_iterator cit = m_hash2blkidx.find(_blk.prevId); if (m_hash2blkidx.end() == cit) { - cerr << "ERROR: can't find previous block with id \"" << _blk.prev_id << "\"" << endl; + cerr << "ERROR: can't find previous block with id \"" << _blk.prevId << "\"" << endl; return false; } diff --git a/tests/core_proxy/core_proxy.h b/tests/core_proxy/core_proxy.h index 19abcafd..785b2e40 100644 --- a/tests/core_proxy/core_proxy.h +++ b/tests/core_proxy/core_proxy.h @@ -17,11 +17,13 @@ #pragma once +#include + #include #include "cryptonote_core/cryptonote_basic_impl.h" +#include "cryptonote_core/Currency.h" #include "cryptonote_core/verification_context.h" -#include namespace tests { @@ -29,33 +31,38 @@ namespace tests size_t height; crypto::hash id; crypto::hash longhash; - cryptonote::block blk; + cryptonote::Block blk; cryptonote::blobdata blob; - std::list txes; + std::list txes; block_index() : height(0), id(cryptonote::null_hash), longhash(cryptonote::null_hash) { } - block_index(size_t _height, const crypto::hash &_id, const crypto::hash &_longhash, const cryptonote::block &_blk, const cryptonote::blobdata &_blob, const std::list &_txes) + block_index(size_t _height, const crypto::hash &_id, const crypto::hash &_longhash, const cryptonote::Block &_blk, const cryptonote::blobdata &_blob, const std::list &_txes) : height(_height), id(_id), longhash(_longhash), blk(_blk), blob(_blob), txes(_txes) { } }; class proxy_core { - cryptonote::block m_genesis; - std::list m_known_block_list; - std::unordered_map m_hash2blkidx; + const cryptonote::Currency& m_currency; + cryptonote::Block m_genesis; + std::list m_known_block_list; + std::unordered_map m_hash2blkidx; - crypto::hash m_lastblk; - std::list txes; + crypto::hash m_lastblk; + std::list txes; - crypto::cn_context m_cn_context; + crypto::cn_context m_cn_context; - bool add_block(const crypto::hash &_id, const crypto::hash &_longhash, const cryptonote::block &_blk, const cryptonote::blobdata &_blob); - void build_short_history(std::list &m_history, const crypto::hash &m_start); + bool add_block(const crypto::hash &_id, const crypto::hash &_longhash, const cryptonote::Block &_blk, const cryptonote::blobdata &_blob); + void build_short_history(std::list &m_history, const crypto::hash &m_start); public: + proxy_core(const cryptonote::Currency& currency) : m_currency(currency) { + } + void on_synchronized(){} uint64_t get_current_blockchain_height(){return 1;} + const cryptonote::Currency& currency() const { return m_currency; } bool init(const boost::program_options::variables_map& vm); bool deinit(){return true;} bool get_short_chain_history(std::list& ids); @@ -63,9 +70,9 @@ namespace tests bool have_block(const crypto::hash& id); bool get_blockchain_top(uint64_t& height, crypto::hash& top_id); bool handle_incoming_tx(const cryptonote::blobdata& tx_blob, cryptonote::tx_verification_context& tvc, bool keeped_by_block); - bool handle_incoming_block(const cryptonote::blobdata& block_blob, cryptonote::block_verification_context& bvc, bool update_miner_blocktemplate = true); - void pause_mine(){} - void resume_mine(){} + bool handle_incoming_block_blob(const cryptonote::blobdata& block_blob, cryptonote::block_verification_context& bvc, bool control_miner, bool relay_block); + void pause_mining(){} + void update_block_template_and_resume_mining(){} bool on_idle(){return true;} bool find_blockchain_supplement(const std::list& qblock_ids, cryptonote::NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp){return true;} bool handle_get_objects(cryptonote::NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote::NOTIFY_RESPONSE_GET_OBJECTS::request& rsp, cryptonote::cryptonote_connection_context& context){return true;} diff --git a/tests/core_tests/TestGenerator.h b/tests/core_tests/TestGenerator.h new file mode 100644 index 00000000..ee627426 --- /dev/null +++ b/tests/core_tests/TestGenerator.h @@ -0,0 +1,119 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include "chaingen.h" + +#include "cryptonote_core/Currency.h" +#include "TransactionBuilder.h" + +class TestGenerator { +public: + TestGenerator(const cryptonote::Currency& currency, std::vector& eventsRef) : + generator(currency), + events(eventsRef) { + minerAccount.generate(); + generator.constructBlock(genesisBlock, minerAccount, 1338224400); + events.push_back(genesisBlock); + lastBlock = genesisBlock; + } + + const cryptonote::Currency& currency() const { return generator.currency(); } + + void makeNextBlock(const std::list& txs = std::list()) { + cryptonote::Block block; + generator.constructBlock(block, lastBlock, minerAccount, txs); + events.push_back(block); + lastBlock = block; + } + + void makeNextBlock(const cryptonote::Transaction& tx) { + std::list txs; + txs.push_back(tx); + makeNextBlock(txs); + } + + void generateBlocks() { + generateBlocks(currency().minedMoneyUnlockWindow()); + } + + void generateBlocks(size_t count, uint8_t majorVersion = cryptonote::BLOCK_MAJOR_VERSION_1) { + while (count--) { + cryptonote::Block next; + generator.constructBlockManually(next, lastBlock, minerAccount, test_generator::bf_major_ver, majorVersion); + lastBlock = next; + events.push_back(next); + } + } + + TransactionBuilder createTxBuilder(const cryptonote::account_base& from, const cryptonote::account_base& to, uint64_t amount, uint64_t fee) { + + std::vector sources; + std::vector destinations; + + fillTxSourcesAndDestinations(sources, destinations, from, to, amount, fee); + + TransactionBuilder builder(generator.currency()); + + builder.setInput(sources, from.get_keys()); + builder.setOutput(destinations); + + return builder; + } + + void fillTxSourcesAndDestinations( + std::vector& sources, + std::vector& destinations, + const cryptonote::account_base& from, const cryptonote::account_base& to, uint64_t amount, uint64_t fee, size_t nmix = 0) { + fill_tx_sources_and_destinations(events, lastBlock, from, to, amount, fee, nmix, sources, destinations); + } + + void constructTxToKey( + cryptonote::Transaction& tx, + const cryptonote::account_base& from, + const cryptonote::account_base& to, + uint64_t amount, + uint64_t fee, + size_t nmix = 0) { + construct_tx_to_key(events, tx, lastBlock, from, to, amount, fee, nmix); + } + + void addEvent(const test_event_entry& e) { + events.push_back(e); + } + + void addCallback(const std::string& name) { + callback_entry cb; + cb.callback_name = name; + events.push_back(cb); + } + + void addCheckAccepted() { + addCallback("check_block_accepted"); + } + + void addCheckPurged() { + addCallback("check_block_purged"); + } + + test_generator generator; + cryptonote::Block genesisBlock; + cryptonote::Block lastBlock; + cryptonote::account_base minerAccount; + std::vector& events; +}; diff --git a/tests/core_tests/TransactionBuilder.cpp b/tests/core_tests/TransactionBuilder.cpp new file mode 100644 index 00000000..37ab1b5a --- /dev/null +++ b/tests/core_tests/TransactionBuilder.cpp @@ -0,0 +1,186 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "TransactionBuilder.h" + +using namespace cryptonote; + + +TransactionBuilder::TransactionBuilder(const cryptonote::Currency& currency, uint64_t unlockTime) + : m_currency(currency), m_version(cryptonote::CURRENT_TRANSACTION_VERSION), m_unlockTime(unlockTime), m_txKey(KeyPair::generate()) {} + +TransactionBuilder& TransactionBuilder::newTxKeys() { + m_txKey = KeyPair::generate(); + return *this; +} + + +TransactionBuilder& TransactionBuilder::setInput(const std::vector& sources, const cryptonote::account_keys& senderKeys) { + m_sources = sources; + m_senderKeys = senderKeys; + return *this; +} + +TransactionBuilder& TransactionBuilder::addMultisignatureInput(const cryptonote::TransactionInputMultisignature& input, const TransactionBuilder::KeysVector& keys) { + + MultisignatureSource src; + src.input = input; + src.keys = keys; + m_msigSources.push_back(src); + + return *this; +} + +TransactionBuilder& TransactionBuilder::setOutput(const std::vector& destinations) { + m_destinations = destinations; + return *this; +} + +TransactionBuilder& TransactionBuilder::addOutput(const cryptonote::tx_destination_entry& dest) { + m_destinations.push_back(dest); + return *this; +} + +TransactionBuilder& TransactionBuilder::addMultisignatureOut(uint64_t amount, const KeysVector& keys, uint32_t required) { + + cryptonote::TransactionOutputMultisignature target; + + for (const auto& key : keys) { + target.keys.push_back(key.m_account_address.m_spendPublicKey); + } + + target.requiredSignatures = required; + + MultisignatureDestination dst; + + dst.amount = amount; + dst.output = target; + + m_msigDestinations.push_back(dst); + + return *this; +} + +Transaction TransactionBuilder::build() const { + crypto::hash prefixHash; + + Transaction tx; + add_tx_pub_key_to_extra(tx, m_txKey.pub); + + tx.version = m_version; + tx.unlockTime = m_unlockTime; + + std::vector contexts; + + fillInputs(tx, contexts); + fillOutputs(tx); + + get_transaction_prefix_hash(tx, prefixHash); + + signSources(prefixHash, contexts, tx); + + return tx; +} + +void TransactionBuilder::fillInputs(Transaction& tx, std::vector& contexts) const { + for (const tx_source_entry& src_entr : m_sources) { + contexts.push_back(KeyPair()); + KeyPair& in_ephemeral = contexts.back(); + crypto::key_image img; + generate_key_image_helper(m_senderKeys, src_entr.real_out_tx_key, src_entr.real_output_in_tx_index, in_ephemeral, img); + + // put key image into tx input + TransactionInputToKey input_to_key; + input_to_key.amount = src_entr.amount; + input_to_key.keyImage = img; + + // fill outputs array and use relative offsets + for (const tx_source_entry::output_entry& out_entry : src_entr.outputs) { + input_to_key.keyOffsets.push_back(out_entry.first); + } + + input_to_key.keyOffsets = absolute_output_offsets_to_relative(input_to_key.keyOffsets); + tx.vin.push_back(input_to_key); + } + + for (const auto& msrc : m_msigSources) { + tx.vin.push_back(msrc.input); + } +} + +void TransactionBuilder::fillOutputs(Transaction& tx) const { + size_t output_index = 0; + + for(const auto& dst_entr : m_destinations) { + crypto::key_derivation derivation; + crypto::public_key out_eph_public_key; + crypto::generate_key_derivation(dst_entr.addr.m_viewPublicKey, m_txKey.sec, derivation); + crypto::derive_public_key(derivation, output_index, dst_entr.addr.m_spendPublicKey, out_eph_public_key); + + TransactionOutput out; + out.amount = dst_entr.amount; + TransactionOutputToKey tk; + tk.key = out_eph_public_key; + out.target = tk; + tx.vout.push_back(out); + output_index++; + } + + for (const auto& mdst : m_msigDestinations) { + TransactionOutput out; + out.amount = mdst.amount; + out.target = mdst.output; + tx.vout.push_back(out); + } +} + + +void TransactionBuilder::signSources(const crypto::hash& prefixHash, const std::vector& contexts, Transaction& tx) const { + + tx.signatures.clear(); + + size_t i = 0; + + // sign TransactionInputToKey sources + for (const auto& src_entr : m_sources) { + std::vector keys_ptrs; + + for (const auto& o : src_entr.outputs) { + keys_ptrs.push_back(&o.second); + } + + tx.signatures.push_back(std::vector()); + std::vector& sigs = tx.signatures.back(); + sigs.resize(src_entr.outputs.size()); + generate_ring_signature(prefixHash, boost::get(tx.vin[i]).keyImage, keys_ptrs, contexts[i].sec, src_entr.real_output, sigs.data()); + i++; + } + + // sign multisignature source + for (const auto& msrc : m_msigSources) { + tx.signatures.resize(tx.signatures.size() + 1); + auto& outsigs = tx.signatures.back(); + + for (const auto& key : msrc.keys) { + const auto& pk = key.m_account_address.m_spendPublicKey; + const auto& sk = key.m_spend_secret_key; + crypto::signature sig; + crypto::generate_signature(prefixHash, pk, sk, sig); + outsigs.push_back(sig); + } + } +} diff --git a/tests/core_tests/TransactionBuilder.h b/tests/core_tests/TransactionBuilder.h new file mode 100644 index 00000000..62113258 --- /dev/null +++ b/tests/core_tests/TransactionBuilder.h @@ -0,0 +1,75 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include "cryptonote_core/account.h" +#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_core/Currency.h" + +class TransactionBuilder { +public: + + typedef std::vector KeysVector; + typedef std::vector SignatureVector; + typedef std::vector SignatureMultivector; + + TransactionBuilder(const cryptonote::Currency& currency, uint64_t unlockTime = 0); + + // regenerate transaction keys + TransactionBuilder& newTxKeys(); + + // inputs + TransactionBuilder& setInput(const std::vector& sources, const cryptonote::account_keys& senderKeys); + TransactionBuilder& addMultisignatureInput(const cryptonote::TransactionInputMultisignature& input, const KeysVector& keys); + + // outputs + TransactionBuilder& setOutput(const std::vector& destinations); + TransactionBuilder& addOutput(const cryptonote::tx_destination_entry& dest); + TransactionBuilder& addMultisignatureOut(uint64_t amount, const KeysVector& keys, uint32_t required); + + cryptonote::Transaction build() const; + + std::vector m_sources; + std::vector m_destinations; + +private: + + void fillInputs(cryptonote::Transaction& tx, std::vector& contexts) const; + void fillOutputs(cryptonote::Transaction& tx) const; + void signSources(const crypto::hash& prefixHash, const std::vector& contexts, cryptonote::Transaction& tx) const; + + struct MultisignatureSource { + cryptonote::TransactionInputMultisignature input; + KeysVector keys; + }; + + struct MultisignatureDestination { + uint64_t amount; + cryptonote::TransactionOutputMultisignature output; + }; + + cryptonote::account_keys m_senderKeys; + + std::vector m_msigSources; + std::vector m_msigDestinations; + + size_t m_version; + uint64_t m_unlockTime; + cryptonote::KeyPair m_txKey; + const cryptonote::Currency& m_currency; +}; diff --git a/tests/core_tests/block_reward.cpp b/tests/core_tests/block_reward.cpp index 918a2336..99a370ad 100644 --- a/tests/core_tests/block_reward.cpp +++ b/tests/core_tests/block_reward.cpp @@ -15,78 +15,24 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . -#include "chaingen.h" -#include "chaingen_tests_list.h" - #include "block_reward.h" +// epee +#include "misc_language.h" + using namespace epee; using namespace cryptonote; namespace { - bool construct_miner_tx_by_size(transaction& miner_tx, uint64_t height, uint64_t already_generated_coins, - const account_public_address& miner_address, std::vector& block_sizes, size_t target_tx_size, - size_t target_block_size, uint64_t fee = 0) - { - if (!construct_miner_tx(height, misc_utils::median(block_sizes), already_generated_coins, target_block_size, fee, miner_address, miner_tx)) - return false; - - size_t current_size = get_object_blobsize(miner_tx); - size_t try_count = 0; - while (target_tx_size != current_size) - { - ++try_count; - if (10 < try_count) - return false; - - if (target_tx_size < current_size) - { - size_t diff = current_size - target_tx_size; - if (diff <= miner_tx.extra.size()) - miner_tx.extra.resize(miner_tx.extra.size() - diff); - else - return false; - } - else - { - size_t diff = target_tx_size - current_size; - miner_tx.extra.resize(miner_tx.extra.size() + diff); - } - - current_size = get_object_blobsize(miner_tx); - } - - return true; - } - - bool construct_max_size_block(test_generator& generator, block& blk, const block& blk_prev, const account_base& miner_account, - size_t median_block_count = CRYPTONOTE_REWARD_BLOCKS_WINDOW) - { - std::vector block_sizes; - generator.get_last_n_block_sizes(block_sizes, get_block_hash(blk_prev), median_block_count); - - size_t median = misc_utils::median(block_sizes); - median = std::max(median, static_cast(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE)); - - transaction miner_tx; - bool r = construct_miner_tx_by_size(miner_tx, get_block_height(blk_prev) + 1, generator.get_already_generated_coins(blk_prev), - miner_account.get_keys().m_account_address, block_sizes, 2 * median, 2 * median); - if (!r) - return false; - - return generator.construct_block_manually(blk, blk_prev, miner_account, test_generator::bf_miner_tx, 0, 0, 0, - crypto::hash(), 0, miner_tx); - } - - bool rewind_blocks(std::vector& events, test_generator& generator, block& blk, const block& blk_prev, + bool rewind_blocks(std::vector& events, test_generator& generator, Block& blk, const Block& blk_prev, const account_base& miner_account, size_t block_count) { blk = blk_prev; for (size_t i = 0; i < block_count; ++i) { - block blk_i; - if (!construct_max_size_block(generator, blk_i, blk, miner_account)) + Block blk_i; + if (!generator.constructMaxSizeBlock(blk_i, blk, miner_account)) return false; events.push_back(blk_i); @@ -96,7 +42,7 @@ namespace return true; } - uint64_t get_tx_out_amount(const transaction& tx) + uint64_t get_tx_out_amount(const Transaction& tx) { uint64_t amount = 0; BOOST_FOREACH(auto& o, tx.vout) @@ -106,8 +52,11 @@ namespace } gen_block_reward::gen_block_reward() - : m_invalid_block_index(0) -{ + : m_invalid_block_index(0) { + cryptonote::CurrencyBuilder currencyBuilder; + currencyBuilder.maxBlockSizeInitial(std::numeric_limits::max() / 2); + m_currency = currencyBuilder.currency(); + REGISTER_CALLBACK_METHOD(gen_block_reward, mark_invalid_block); REGISTER_CALLBACK_METHOD(gen_block_reward, mark_checked_block); REGISTER_CALLBACK_METHOD(gen_block_reward, check_block_rewards); @@ -122,28 +71,34 @@ bool gen_block_reward::generate(std::vector& events) const DO_CALLBACK(events, "mark_checked_block"); MAKE_ACCOUNT(events, bob_account); - // Test: miner transactions without outputs (block reward == 0) - block blk_0r; - if (!rewind_blocks(events, generator, blk_0r, blk_0, miner_account, CRYPTONOTE_REWARD_BLOCKS_WINDOW)) + // Test case 1: miner transactions without outputs (block reward == 0) + Block blk_0r; + if (!rewind_blocks(events, generator, blk_0r, blk_0, miner_account, m_currency.rewardBlocksWindow())) { return false; + } - // Test: block reward is calculated using median of the latest CRYPTONOTE_REWARD_BLOCKS_WINDOW blocks + // Test: block reward is calculated using median of the latest m_currency.rewardBlocksWindow() blocks DO_CALLBACK(events, "mark_invalid_block"); - block blk_1_bad_1; - if (!construct_max_size_block(generator, blk_1_bad_1, blk_0r, miner_account, CRYPTONOTE_REWARD_BLOCKS_WINDOW + 1)) + Block blk_1_bad_1; + if (!generator.constructMaxSizeBlock(blk_1_bad_1, blk_0r, miner_account, m_currency.rewardBlocksWindow() + 1)) { return false; + } events.push_back(blk_1_bad_1); DO_CALLBACK(events, "mark_invalid_block"); - block blk_1_bad_2; - if (!construct_max_size_block(generator, blk_1_bad_2, blk_0r, miner_account, CRYPTONOTE_REWARD_BLOCKS_WINDOW - 1)) + Block blk_1_bad_2; + if (!generator.constructMaxSizeBlock(blk_1_bad_2, blk_0r, miner_account, m_currency.rewardBlocksWindow() - 1)) { return false; + } events.push_back(blk_1_bad_2); - block blk_1; - if (!construct_max_size_block(generator, blk_1, blk_0r, miner_account)) + // Test 1.2: miner transactions without outputs (block reward == 0) + Block blk_1; + if (!generator.constructMaxSizeBlock(blk_1, blk_0r, miner_account)) { return false; + } events.push_back(blk_1); + // End of Test case 1 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -156,35 +111,35 @@ bool gen_block_reward::generate(std::vector& events) const MAKE_NEXT_BLOCK(events, blk_5, blk_4, miner_account); DO_CALLBACK(events, "mark_checked_block"); - block blk_5r; - if (!rewind_blocks(events, generator, blk_5r, blk_5, miner_account, CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW)) + Block blk_5r; + if (!rewind_blocks(events, generator, blk_5r, blk_5, miner_account, m_currency.minedMoneyUnlockWindow())) return false; // Test: fee increases block reward - transaction tx_0(construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 3 * TESTS_DEFAULT_FEE)); + Transaction tx_0(construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 3 * m_currency.minimumFee())); MAKE_NEXT_BLOCK_TX1(events, blk_6, blk_5r, miner_account, tx_0); DO_CALLBACK(events, "mark_checked_block"); // Test: fee from all block transactions increase block reward - std::list txs_0; - txs_0.push_back(construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 5 * TESTS_DEFAULT_FEE)); - txs_0.push_back(construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 7 * TESTS_DEFAULT_FEE)); + std::list txs_0; + txs_0.push_back(construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 5 * m_currency.minimumFee())); + txs_0.push_back(construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 7 * m_currency.minimumFee())); MAKE_NEXT_BLOCK_TX_LIST(events, blk_7, blk_6, miner_account, txs_0); DO_CALLBACK(events, "mark_checked_block"); // Test: block reward == transactions fee { - transaction tx_1 = construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 11 * TESTS_DEFAULT_FEE); - transaction tx_2 = construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 13 * TESTS_DEFAULT_FEE); + Transaction tx_1 = construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 11 * m_currency.minimumFee()); + Transaction tx_2 = construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 13 * m_currency.minimumFee()); size_t txs_1_size = get_object_blobsize(tx_1) + get_object_blobsize(tx_2); uint64_t txs_fee = get_tx_fee(tx_1) + get_tx_fee(tx_2); std::vector block_sizes; - generator.get_last_n_block_sizes(block_sizes, get_block_hash(blk_7), CRYPTONOTE_REWARD_BLOCKS_WINDOW); + generator.getLastNBlockSizes(block_sizes, get_block_hash(blk_7), m_currency.rewardBlocksWindow()); size_t median = misc_utils::median(block_sizes); - transaction miner_tx; - bool r = construct_miner_tx_by_size(miner_tx, get_block_height(blk_7) + 1, generator.get_already_generated_coins(blk_7), + Transaction miner_tx; + bool r = constructMinerTxBySize(m_currency, miner_tx, get_block_height(blk_7) + 1, generator.getAlreadyGeneratedCoins(blk_7), miner_account.get_keys().m_account_address, block_sizes, 2 * median - txs_1_size, 2 * median, txs_fee); if (!r) return false; @@ -193,9 +148,9 @@ bool gen_block_reward::generate(std::vector& events) const txs_1_hashes.push_back(get_transaction_hash(tx_1)); txs_1_hashes.push_back(get_transaction_hash(tx_2)); - block blk_8; - generator.construct_block_manually(blk_8, blk_7, miner_account, test_generator::bf_miner_tx | test_generator::bf_tx_hashes, - 0, 0, 0, crypto::hash(), 0, miner_tx, txs_1_hashes, txs_1_size); + Block blk_8; + generator.constructBlockManually(blk_8, blk_7, miner_account, test_generator::bf_miner_tx | test_generator::bf_tx_hashes, + 0, 0, 0, crypto::hash(), 0, miner_tx, txs_1_hashes, txs_1_size, txs_fee); events.push_back(blk_8); DO_CALLBACK(events, "mark_checked_block"); @@ -206,7 +161,7 @@ bool gen_block_reward::generate(std::vector& events) const return true; } -bool gen_block_reward::check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*blk*/) +bool gen_block_reward::check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::Block& /*blk*/) { if (m_invalid_block_index == event_idx) { @@ -236,28 +191,28 @@ bool gen_block_reward::check_block_rewards(cryptonote::core& /*c*/, size_t /*ev_ DEFINE_TESTS_ERROR_CONTEXT("gen_block_reward_without_txs::check_block_rewards"); std::array blk_rewards; - blk_rewards[0] = MONEY_SUPPLY >> 18; + blk_rewards[0] = m_currency.moneySupply() >> m_currency.emissionSpeedFactor(); uint64_t cumulative_reward = blk_rewards[0]; for (size_t i = 1; i < blk_rewards.size(); ++i) { - blk_rewards[i] = (MONEY_SUPPLY - cumulative_reward) >> 18; + blk_rewards[i] = (m_currency.moneySupply() - cumulative_reward) >> m_currency.emissionSpeedFactor(); cumulative_reward += blk_rewards[i]; } for (size_t i = 0; i < 5; ++i) { - block blk_i = boost::get(events[m_checked_blocks_indices[i]]); - CHECK_EQ(blk_rewards[i], get_tx_out_amount(blk_i.miner_tx)); + Block blk_i = boost::get(events[m_checked_blocks_indices[i]]); + CHECK_EQ(blk_rewards[i], get_tx_out_amount(blk_i.minerTx)); } - block blk_n1 = boost::get(events[m_checked_blocks_indices[5]]); - CHECK_EQ(blk_rewards[5] + 3 * TESTS_DEFAULT_FEE, get_tx_out_amount(blk_n1.miner_tx)); + Block blk_n1 = boost::get(events[m_checked_blocks_indices[5]]); + CHECK_EQ(blk_rewards[5] + 3 * m_currency.minimumFee(), get_tx_out_amount(blk_n1.minerTx)); - block blk_n2 = boost::get(events[m_checked_blocks_indices[6]]); - CHECK_EQ(blk_rewards[6] + (5 + 7) * TESTS_DEFAULT_FEE, get_tx_out_amount(blk_n2.miner_tx)); + Block blk_n2 = boost::get(events[m_checked_blocks_indices[6]]); + CHECK_EQ(blk_rewards[6] + (5 + 7) * m_currency.minimumFee(), get_tx_out_amount(blk_n2.minerTx)); - block blk_n3 = boost::get(events[m_checked_blocks_indices[7]]); - CHECK_EQ((11 + 13) * TESTS_DEFAULT_FEE, get_tx_out_amount(blk_n3.miner_tx)); + Block blk_n3 = boost::get(events[m_checked_blocks_indices[7]]); + CHECK_EQ((11 + 13) * m_currency.minimumFee(), get_tx_out_amount(blk_n3.minerTx)); return true; } diff --git a/tests/core_tests/block_reward.h b/tests/core_tests/block_reward.h index e23205ab..564fbd61 100644 --- a/tests/core_tests/block_reward.h +++ b/tests/core_tests/block_reward.h @@ -24,7 +24,7 @@ struct gen_block_reward : public test_chain_unit_base bool generate(std::vector& events) const; - bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& blk); + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::Block& blk); bool mark_invalid_block(cryptonote::core& c, size_t ev_index, const std::vector& events); bool mark_checked_block(cryptonote::core& c, size_t ev_index, const std::vector& events); diff --git a/tests/core_tests/block_validation.cpp b/tests/core_tests/block_validation.cpp index be58059a..c01551ff 100644 --- a/tests/core_tests/block_validation.cpp +++ b/tests/core_tests/block_validation.cpp @@ -15,31 +15,35 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . -#include "chaingen.h" -#include "chaingen_tests_list.h" +#include "block_validation.h" +#include "TestGenerator.h" using namespace epee; using namespace cryptonote; -namespace -{ - bool lift_up_difficulty(std::vector& events, std::vector& timestamps, - std::vector& cummulative_difficulties, test_generator& generator, - size_t new_block_count, const block blk_last, const account_base& miner_account) - { - difficulty_type commulative_diffic = cummulative_difficulties.empty() ? 0 : cummulative_difficulties.back(); - block blk_prev = blk_last; - for (size_t i = 0; i < new_block_count; ++i) - { - block blk_next; - difficulty_type diffic = next_difficulty(timestamps, cummulative_difficulties); - if (!generator.construct_block_manually(blk_next, blk_prev, miner_account, - test_generator::bf_timestamp | test_generator::bf_diffic, 0, 0, blk_prev.timestamp, crypto::hash(), diffic)) +#define BLOCK_VALIDATION_INIT_GENERATE() \ + GENERATE_ACCOUNT(miner_account); \ + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, 1338224400); + +namespace { + bool lift_up_difficulty(const cryptonote::Currency& currency, std::vector& events, + std::vector& timestamps, + std::vector& cummulative_difficulties, test_generator& generator, + size_t new_block_count, const cryptonote::Block blk_last, + const cryptonote::account_base& miner_account, uint8_t block_major_version) { + cryptonote::difficulty_type commulative_diffic = cummulative_difficulties.empty() ? 0 : cummulative_difficulties.back(); + cryptonote::Block blk_prev = blk_last; + for (size_t i = 0; i < new_block_count; ++i) { + cryptonote::Block blk_next; + cryptonote::difficulty_type diffic = currency.nextDifficulty(timestamps, cummulative_difficulties); + if (!generator.constructBlockManually(blk_next, blk_prev, miner_account, + test_generator::bf_major_ver | test_generator::bf_timestamp | test_generator::bf_diffic, + block_major_version, 0, blk_prev.timestamp, crypto::hash(), diffic)) { return false; + } commulative_diffic += diffic; - if (timestamps.size() == DIFFICULTY_WINDOW) - { + if (timestamps.size() == currency.difficultyWindow()) { timestamps.erase(timestamps.begin()); cummulative_difficulties.erase(cummulative_difficulties.begin()); } @@ -52,34 +56,84 @@ namespace return true; } + + bool getParentBlockSize(const cryptonote::Block& block, size_t& size) { + auto serializer = cryptonote::makeParentBlockSerializer(block, false, false); + if (!cryptonote::get_object_blobsize(serializer, size)) { + LOG_ERROR("Failed to get size of parent block"); + return false; + } + return true; + } + + bool adjustParentBlockSize(cryptonote::Block& block, size_t targetSize) { + size_t parentBlockSize; + if (!getParentBlockSize(block, parentBlockSize)) { + return false; + } + + if (parentBlockSize > targetSize) { + LOG_ERROR("Parent block size is " << parentBlockSize << " bytes that is already greater than target size " << targetSize << " bytes"); + return false; + } + + block.parentBlock.minerTx.extra.resize(block.parentBlock.minerTx.extra.size() + (targetSize - parentBlockSize)); + + if (!getParentBlockSize(block, parentBlockSize)) { + return false; + } + + if (parentBlockSize > targetSize) { + if (block.parentBlock.minerTx.extra.size() < parentBlockSize - targetSize) { + LOG_ERROR("Failed to adjust parent block size to " << targetSize); + return false; + } + + block.parentBlock.minerTx.extra.resize(block.parentBlock.minerTx.extra.size() - (parentBlockSize - targetSize)); + + if (!getParentBlockSize(block, parentBlockSize)) { + return false; + } + + if (parentBlockSize + 1 == targetSize) { + block.timestamp = std::max(block.timestamp, UINT64_C(1)) << 7; + if (!getParentBlockSize(block, parentBlockSize)) { + return false; + } + } + } + + if (parentBlockSize != targetSize) { + LOG_ERROR("Failed to adjust parent block size to " << targetSize); + return false; + } + + return true; + } } -#define BLOCK_VALIDATION_INIT_GENERATE() \ - GENERATE_ACCOUNT(miner_account); \ - MAKE_GENESIS_BLOCK(events, blk_0, miner_account, 1338224400); - -//---------------------------------------------------------------------------------------------------------------------- -// Tests - -bool gen_block_big_major_version::generate(std::vector& events) const -{ - BLOCK_VALIDATION_INIT_GENERATE(); - - block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_major_ver, CURRENT_BLOCK_MAJOR_VERSION + 1); - events.push_back(blk_1); - - DO_CALLBACK(events, "check_block_purged"); +bool TestBlockMajorVersionAccepted::generate(std::vector& events) const { + TestGenerator bg(m_currency, events); + bg.generateBlocks(1, m_blockMajorVersion); + DO_CALLBACK(events, "check_block_accepted"); return true; } -bool gen_block_big_minor_version::generate(std::vector& events) const -{ +bool TestBlockMajorVersionRejected::generate(std::vector& events) const { + TestGenerator bg(m_currency, events); + bg.generateBlocks(1, m_blockGeneratedVersion); + DO_CALLBACK(events, "check_block_purged"); + return true; +} + +bool TestBlockBigMinorVersion::generate(std::vector& events) const { BLOCK_VALIDATION_INIT_GENERATE(); - block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_minor_ver, 0, CURRENT_BLOCK_MINOR_VERSION + 1); + cryptonote::Block blk_1; + generator.constructBlockManually(blk_1, blk_0, miner_account, + test_generator::bf_major_ver | test_generator::bf_minor_ver, m_blockMajorVersion, BLOCK_MINOR_VERSION_0 + 1); + events.push_back(blk_1); DO_CALLBACK(events, "check_block_accepted"); @@ -90,10 +144,14 @@ bool gen_block_big_minor_version::generate(std::vector& events bool gen_block_ts_not_checked::generate(std::vector& events) const { BLOCK_VALIDATION_INIT_GENERATE(); - REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_account, BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW - 2); - block blk_1; - generator.construct_block_manually(blk_1, blk_0r, miner_account, test_generator::bf_timestamp, 0, 0, blk_0.timestamp - 60 * 60); + generator.defaultMajorVersion = m_blockMajorVersion; + + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_account, m_currency.timestampCheckWindow() - 2); + + Block blk_1; + generator.constructBlockManually(blk_1, blk_0r, miner_account, + test_generator::bf_major_ver | test_generator::bf_timestamp, m_blockMajorVersion, 0, blk_0.timestamp - 60 * 60); events.push_back(blk_1); DO_CALLBACK(events, "check_block_accepted"); @@ -104,11 +162,15 @@ bool gen_block_ts_not_checked::generate(std::vector& events) c bool gen_block_ts_in_past::generate(std::vector& events) const { BLOCK_VALIDATION_INIT_GENERATE(); - REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_account, BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW - 1); - uint64_t ts_below_median = boost::get(events[BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW / 2 - 1]).timestamp; - block blk_1; - generator.construct_block_manually(blk_1, blk_0r, miner_account, test_generator::bf_timestamp, 0, 0, ts_below_median); + generator.defaultMajorVersion = m_blockMajorVersion; + + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_account, m_currency.timestampCheckWindow() - 1); + + uint64_t ts_below_median = boost::get(events[m_currency.timestampCheckWindow() / 2 - 1]).timestamp; + Block blk_1; + generator.constructBlockManually(blk_1, blk_0r, miner_account, + test_generator::bf_major_ver | test_generator::bf_timestamp, m_blockMajorVersion, 0, ts_below_median); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -116,12 +178,13 @@ bool gen_block_ts_in_past::generate(std::vector& events) const return true; } -bool gen_block_ts_in_future::generate(std::vector& events) const +bool gen_block_ts_in_future_rejected::generate(std::vector& events) const { BLOCK_VALIDATION_INIT_GENERATE(); - block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_timestamp, 0, 0, time(NULL) + 60*60 + CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT); + Block blk_1; + generator.constructBlockManually(blk_1, blk_0, miner_account, test_generator::bf_major_ver | test_generator::bf_timestamp, + m_blockMajorVersion, 0, time(NULL) + 60 * 60 + m_currency.blockFutureTimeLimit()); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -129,14 +192,30 @@ bool gen_block_ts_in_future::generate(std::vector& events) con return true; } +bool gen_block_ts_in_future_accepted::generate(std::vector& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + Block blk_1; + generator.constructBlockManually(blk_1, blk_0, miner_account, test_generator::bf_major_ver | test_generator::bf_timestamp, + m_blockMajorVersion, 0, time(NULL) - 60 + m_currency.blockFutureTimeLimit()); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_accepted"); + + return true; +} + + bool gen_block_invalid_prev_id::generate(std::vector& events) const { BLOCK_VALIDATION_INIT_GENERATE(); - block blk_1; + Block blk_1; crypto::hash prev_id = get_block_hash(blk_0); reinterpret_cast(prev_id) ^= 1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_prev_id, 0, 0, 0, prev_id); + generator.constructBlockManually(blk_1, blk_0, miner_account, + test_generator::bf_major_ver | test_generator::bf_prev_id, m_blockMajorVersion, 0, 0, prev_id); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -144,7 +223,7 @@ bool gen_block_invalid_prev_id::generate(std::vector& events) return true; } -bool gen_block_invalid_prev_id::check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*blk*/) +bool gen_block_invalid_prev_id::check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::Block& /*blk*/) { if (1 == event_idx) return bvc.m_marked_as_orphaned && !bvc.m_added_to_main_chain && !bvc.m_verifivation_failed; @@ -158,21 +237,23 @@ bool gen_block_invalid_nonce::generate(std::vector& events) co std::vector timestamps; std::vector commulative_difficulties; - if (!lift_up_difficulty(events, timestamps, commulative_difficulties, generator, 2, blk_0, miner_account)) + if (!lift_up_difficulty(m_currency, events, timestamps, commulative_difficulties, generator, 2, blk_0, miner_account, + m_blockMajorVersion)) { return false; + } // Create invalid nonce - difficulty_type diffic = next_difficulty(timestamps, commulative_difficulties); + difficulty_type diffic = m_currency.nextDifficulty(timestamps, commulative_difficulties); assert(1 < diffic); - const block& blk_last = boost::get(events.back()); + const Block& blk_last = boost::get(events.back()); uint64_t timestamp = blk_last.timestamp; - block blk_3; + Block blk_3; do { ++timestamp; - blk_3.miner_tx.set_null(); - if (!generator.construct_block_manually(blk_3, blk_last, miner_account, - test_generator::bf_diffic | test_generator::bf_timestamp, 0, 0, timestamp, crypto::hash(), diffic)) + blk_3.minerTx.clear(); + if (!generator.constructBlockManually(blk_3, blk_last, miner_account, + test_generator::bf_major_ver | test_generator::bf_diffic | test_generator::bf_timestamp, m_blockMajorVersion, 0, timestamp, crypto::hash(), diffic)) return false; } while (0 == blk_3.nonce); @@ -186,11 +267,12 @@ bool gen_block_no_miner_tx::generate(std::vector& events) cons { BLOCK_VALIDATION_INIT_GENERATE(); - transaction miner_tx; - miner_tx.set_null(); + Transaction miner_tx; + miner_tx.clear(); - block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + Block blk_1; + generator.constructBlockManually(blk_1, blk_0, miner_account, + test_generator::bf_major_ver | test_generator::bf_miner_tx, m_blockMajorVersion, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -203,10 +285,11 @@ bool gen_block_unlock_time_is_low::generate(std::vector& event BLOCK_VALIDATION_INIT_GENERATE(); MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); - --miner_tx.unlock_time; + --miner_tx.unlockTime; - block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + Block blk_1; + generator.constructBlockManually(blk_1, blk_0, miner_account, + test_generator::bf_major_ver | test_generator::bf_miner_tx, m_blockMajorVersion, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -219,10 +302,11 @@ bool gen_block_unlock_time_is_high::generate(std::vector& even BLOCK_VALIDATION_INIT_GENERATE(); MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); - ++miner_tx.unlock_time; + ++miner_tx.unlockTime; - block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + Block blk_1; + generator.constructBlockManually(blk_1, blk_0, miner_account, + test_generator::bf_major_ver | test_generator::bf_miner_tx, m_blockMajorVersion, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -235,10 +319,11 @@ bool gen_block_unlock_time_is_timestamp_in_past::generate(std::vector& events) co BLOCK_VALIDATION_INIT_GENERATE(); MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); - boost::get(miner_tx.vin[0]).height--; + boost::get(miner_tx.vin[0]).height--; - block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + Block blk_1; + generator.constructBlockManually(blk_1, blk_0, miner_account, + test_generator::bf_major_ver | test_generator::bf_miner_tx, m_blockMajorVersion, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -283,10 +370,11 @@ bool gen_block_height_is_high::generate(std::vector& events) c BLOCK_VALIDATION_INIT_GENERATE(); MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); - boost::get(miner_tx.vin[0]).height++; + boost::get(miner_tx.vin[0]).height++; - block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + Block blk_1; + generator.constructBlockManually(blk_1, blk_0, miner_account, + test_generator::bf_major_ver | test_generator::bf_miner_tx, m_blockMajorVersion, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -300,12 +388,13 @@ bool gen_block_miner_tx_has_2_tx_gen_in::generate(std::vector& MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); - txin_gen in; + TransactionInputGenerate in; in.height = get_block_height(blk_0) + 1; miner_tx.vin.push_back(in); - block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + Block blk_1; + generator.constructBlockManually(blk_1, blk_0, miner_account, + test_generator::bf_major_ver | test_generator::bf_miner_tx, m_blockMajorVersion, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -316,15 +405,18 @@ bool gen_block_miner_tx_has_2_tx_gen_in::generate(std::vector& bool gen_block_miner_tx_has_2_in::generate(std::vector& events) const { BLOCK_VALIDATION_INIT_GENERATE(); + + generator.defaultMajorVersion = m_blockMajorVersion; + REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); GENERATE_ACCOUNT(alice); tx_source_entry se; - se.amount = blk_0.miner_tx.vout[0].amount; - se.outputs.push_back(std::make_pair(0, boost::get(blk_0.miner_tx.vout[0].target).key)); + se.amount = blk_0.minerTx.vout[0].amount; + se.outputs.push_back(std::make_pair(0, boost::get(blk_0.minerTx.vout[0].target).key)); se.real_output = 0; - se.real_out_tx_key = get_tx_pub_key_from_extra(blk_0.miner_tx); + se.real_out_tx_key = get_tx_pub_key_from_extra(blk_0.minerTx); se.real_output_in_tx_index = 0; std::vector sources; sources.push_back(se); @@ -335,15 +427,16 @@ bool gen_block_miner_tx_has_2_in::generate(std::vector& events std::vector destinations; destinations.push_back(de); - transaction tmp_tx; + Transaction tmp_tx; if (!construct_tx(miner_account.get_keys(), sources, destinations, std::vector(), tmp_tx, 0)) return false; MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); miner_tx.vin.push_back(tmp_tx.vin[0]); - block blk_1; - generator.construct_block_manually(blk_1, blk_0r, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + Block blk_1; + generator.constructBlockManually(blk_1, blk_0r, miner_account, + test_generator::bf_major_ver | test_generator::bf_miner_tx, m_blockMajorVersion, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -355,18 +448,20 @@ bool gen_block_miner_tx_with_txin_to_key::generate(std::vector { BLOCK_VALIDATION_INIT_GENERATE(); + generator.defaultMajorVersion = m_blockMajorVersion; + // This block has only one output - block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_none); + Block blk_1; + generator.constructBlockManually(blk_1, blk_0, miner_account, test_generator::bf_none); events.push_back(blk_1); REWIND_BLOCKS(events, blk_1r, blk_1, miner_account); tx_source_entry se; - se.amount = blk_1.miner_tx.vout[0].amount; - se.outputs.push_back(std::make_pair(0, boost::get(blk_1.miner_tx.vout[0].target).key)); + se.amount = blk_1.minerTx.vout[0].amount; + se.outputs.push_back(std::make_pair(0, boost::get(blk_1.minerTx.vout[0].target).key)); se.real_output = 0; - se.real_out_tx_key = get_tx_pub_key_from_extra(blk_1.miner_tx); + se.real_out_tx_key = get_tx_pub_key_from_extra(blk_1.minerTx); se.real_output_in_tx_index = 0; std::vector sources; sources.push_back(se); @@ -377,15 +472,16 @@ bool gen_block_miner_tx_with_txin_to_key::generate(std::vector std::vector destinations; destinations.push_back(de); - transaction tmp_tx; + Transaction tmp_tx; if (!construct_tx(miner_account.get_keys(), sources, destinations, std::vector(), tmp_tx, 0)) return false; MAKE_MINER_TX_MANUALLY(miner_tx, blk_1); miner_tx.vin[0] = tmp_tx.vin[0]; - block blk_2; - generator.construct_block_manually(blk_2, blk_1r, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + Block blk_2; + generator.constructBlockManually(blk_2, blk_1r, miner_account, + test_generator::bf_major_ver | test_generator::bf_miner_tx, m_blockMajorVersion, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_2); DO_CALLBACK(events, "check_block_purged"); @@ -400,8 +496,9 @@ bool gen_block_miner_tx_out_is_small::generate(std::vector& ev MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); miner_tx.vout[0].amount /= 2; - block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + Block blk_1; + generator.constructBlockManually(blk_1, blk_0, miner_account, + test_generator::bf_major_ver | test_generator::bf_miner_tx, m_blockMajorVersion, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -416,8 +513,9 @@ bool gen_block_miner_tx_out_is_big::generate(std::vector& even MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); miner_tx.vout[0].amount *= 2; - block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + Block blk_1; + generator.constructBlockManually(blk_1, blk_0, miner_account, + test_generator::bf_major_ver | test_generator::bf_miner_tx, m_blockMajorVersion, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -432,8 +530,9 @@ bool gen_block_miner_tx_has_no_out::generate(std::vector& even MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); miner_tx.vout.clear(); - block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + Block blk_1; + generator.constructBlockManually(blk_1, blk_0, miner_account, + test_generator::bf_major_ver | test_generator::bf_miner_tx, m_blockMajorVersion, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -447,22 +546,23 @@ bool gen_block_miner_tx_has_out_to_alice::generate(std::vector GENERATE_ACCOUNT(alice); - keypair txkey; + KeyPair txkey; MAKE_MINER_TX_AND_KEY_MANUALLY(miner_tx, blk_0, &txkey); crypto::key_derivation derivation; crypto::public_key out_eph_public_key; - crypto::generate_key_derivation(alice.get_keys().m_account_address.m_view_public_key, txkey.sec, derivation); - crypto::derive_public_key(derivation, 1, alice.get_keys().m_account_address.m_spend_public_key, out_eph_public_key); + crypto::generate_key_derivation(alice.get_keys().m_account_address.m_viewPublicKey, txkey.sec, derivation); + crypto::derive_public_key(derivation, 1, alice.get_keys().m_account_address.m_spendPublicKey, out_eph_public_key); - tx_out out_to_alice; + TransactionOutput out_to_alice; out_to_alice.amount = miner_tx.vout[0].amount / 2; miner_tx.vout[0].amount -= out_to_alice.amount; - out_to_alice.target = txout_to_key(out_eph_public_key); + out_to_alice.target = TransactionOutputToKey(out_eph_public_key); miner_tx.vout.push_back(out_to_alice); - block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + Block blk_1; + generator.constructBlockManually(blk_1, blk_0, miner_account, + test_generator::bf_major_ver | test_generator::bf_miner_tx, m_blockMajorVersion, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_accepted"); @@ -474,11 +574,13 @@ bool gen_block_has_invalid_tx::generate(std::vector& events) c { BLOCK_VALIDATION_INIT_GENERATE(); + generator.defaultMajorVersion = m_blockMajorVersion; + std::vector tx_hashes; tx_hashes.push_back(crypto::hash()); - block blk_1; - generator.construct_block_manually_tx(blk_1, blk_0, miner_account, tx_hashes, 0); + Block blk_1; + generator.constructBlockManuallyTx(blk_1, blk_0, miner_account, tx_hashes, 0); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -492,22 +594,22 @@ bool gen_block_is_too_big::generate(std::vector& events) const // Creating a huge miner_tx, it will have a lot of outs MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); - static const size_t tx_out_count = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE / 2; + static const size_t tx_out_count = m_currency.blockGrantedFullRewardZone() / 2; uint64_t amount = get_outs_money_amount(miner_tx); uint64_t portion = amount / tx_out_count; uint64_t remainder = amount % tx_out_count; - txout_target_v target = miner_tx.vout[0].target; + TransactionOutputTarget target = miner_tx.vout[0].target; miner_tx.vout.clear(); for (size_t i = 0; i < tx_out_count; ++i) { - tx_out o; + TransactionOutput o; o.amount = portion; o.target = target; miner_tx.vout.push_back(o); } if (0 < remainder) { - tx_out o; + TransactionOutput o; o.amount = remainder; o.target = target; miner_tx.vout.push_back(o); @@ -515,8 +617,9 @@ bool gen_block_is_too_big::generate(std::vector& events) const // Block reward will be incorrect, as it must be reduced if cumulative block size is very big, // but in this test it doesn't matter - block blk_1; - if (!generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx)) + Block blk_1; + if (!generator.constructBlockManually(blk_1, blk_0, miner_account, + test_generator::bf_major_ver | test_generator::bf_miner_tx, m_blockMajorVersion, 0, 0, crypto::hash(), 0, miner_tx)) return false; events.push_back(blk_1); @@ -526,9 +629,39 @@ bool gen_block_is_too_big::generate(std::vector& events) const return true; } -gen_block_invalid_binary_format::gen_block_invalid_binary_format() - : m_corrupt_blocks_begin_idx(0) -{ +bool TestBlockCumulativeSizeExceedsLimit::generate(std::vector& events) const { + BLOCK_VALIDATION_INIT_GENERATE(); + + generator.defaultMajorVersion = m_blockMajorVersion; + + Block prevBlock = blk_0; + for (size_t height = 1; height < 1000; ++height) { + Block block; + if (!generator.constructMaxSizeBlock(block, prevBlock, miner_account)) { + return false; + } + + prevBlock = block; + + if (get_object_blobsize(block.minerTx) <= m_currency.maxBlockCumulativeSize(height)) { + events.push_back(block); + } else { + DO_CALLBACK(events, "markInvalidBlock"); + events.push_back(block); + return true; + } + } + + return false; +} + +gen_block_invalid_binary_format::gen_block_invalid_binary_format(uint8_t blockMajorVersion) : + m_corrupt_blocks_begin_idx(0), + m_blockMajorVersion(blockMajorVersion) { + cryptonote::CurrencyBuilder currencyBuilder; + currencyBuilder.upgradeHeight(blockMajorVersion == cryptonote::BLOCK_MAJOR_VERSION_1 ? UNDEF_HEIGHT : 0); + m_currency = currencyBuilder.currency(); + REGISTER_CALLBACK("check_all_blocks_purged", gen_block_invalid_binary_format::check_all_blocks_purged); REGISTER_CALLBACK("corrupt_blocks_boundary", gen_block_invalid_binary_format::corrupt_blocks_boundary); } @@ -537,14 +670,16 @@ bool gen_block_invalid_binary_format::generate(std::vector& ev { BLOCK_VALIDATION_INIT_GENERATE(); + generator.defaultMajorVersion = m_blockMajorVersion; + std::vector timestamps; std::vector cummulative_difficulties; difficulty_type cummulative_diff = 1; // Unlock blk_0 outputs - block blk_last = blk_0; - assert(CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW < DIFFICULTY_WINDOW); - for (size_t i = 0; i < CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; ++i) + Block blk_last = blk_0; + assert(m_currency.minedMoneyUnlockWindow() < m_currency.difficultyWindow()); + for (size_t i = 0; i < m_currency.minedMoneyUnlockWindow(); ++i) { MAKE_NEXT_BLOCK(events, blk_curr, blk_last, miner_account); timestamps.push_back(blk_curr.timestamp); @@ -556,26 +691,28 @@ bool gen_block_invalid_binary_format::generate(std::vector& ev difficulty_type diffic; do { - blk_last = boost::get(events.back()); - diffic = next_difficulty(timestamps, cummulative_difficulties); - if (!lift_up_difficulty(events, timestamps, cummulative_difficulties, generator, 1, blk_last, miner_account)) + blk_last = boost::get(events.back()); + diffic = m_currency.nextDifficulty(timestamps, cummulative_difficulties); + if (!lift_up_difficulty(m_currency, events, timestamps, cummulative_difficulties, generator, 1, blk_last, + miner_account, m_blockMajorVersion)) { return false; + } std::cout << "Block #" << events.size() << ", difficulty: " << diffic << std::endl; } while (diffic < 1500); - blk_last = boost::get(events.back()); - MAKE_TX(events, tx_0, miner_account, miner_account, MK_COINS(120), boost::get(events[1])); + blk_last = boost::get(events.back()); + MAKE_TX(events, tx_0, miner_account, miner_account, MK_COINS(120), boost::get(events[1])); DO_CALLBACK(events, "corrupt_blocks_boundary"); - block blk_test; + Block blk_test; std::vector tx_hashes; tx_hashes.push_back(get_transaction_hash(tx_0)); size_t txs_size = get_object_blobsize(tx_0); - diffic = next_difficulty(timestamps, cummulative_difficulties); - if (!generator.construct_block_manually(blk_test, blk_last, miner_account, - test_generator::bf_diffic | test_generator::bf_timestamp | test_generator::bf_tx_hashes, 0, 0, blk_last.timestamp, - crypto::hash(), diffic, transaction(), tx_hashes, txs_size)) + diffic = m_currency.nextDifficulty(timestamps, cummulative_difficulties); + if (!generator.constructBlockManually(blk_test, blk_last, miner_account, + test_generator::bf_major_ver | test_generator::bf_diffic | test_generator::bf_timestamp | test_generator::bf_tx_hashes, + m_blockMajorVersion, 0, blk_last.timestamp, crypto::hash(), diffic, Transaction(), tx_hashes, txs_size)) return false; blobdata blob = t_serializable_object_to_blob(blk_test); @@ -597,7 +734,7 @@ bool gen_block_invalid_binary_format::generate(std::vector& ev } bool gen_block_invalid_binary_format::check_block_verification_context(const cryptonote::block_verification_context& bvc, - size_t event_idx, const cryptonote::block& blk) + size_t event_idx, const cryptonote::Block& blk) { if (0 == m_corrupt_blocks_begin_idx || event_idx < m_corrupt_blocks_begin_idx) { @@ -624,3 +761,76 @@ bool gen_block_invalid_binary_format::check_all_blocks_purged(cryptonote::core& return true; } + +bool TestMaxSizeOfParentBlock::generate(std::vector& events) const { + BLOCK_VALIDATION_INIT_GENERATE(); + + cryptonote::Block blk_1; + generator.constructBlockManually(blk_1, blk_0, miner_account, test_generator::bf_major_ver, BLOCK_MAJOR_VERSION_2); + if (!adjustParentBlockSize(blk_1, 2 * 1024)) { + return false; + } + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_accepted"); + + return true; +} + +bool TestBigParentBlock::generate(std::vector& events) const { + BLOCK_VALIDATION_INIT_GENERATE(); + + cryptonote::Block blk_1; + generator.constructBlockManually(blk_1, blk_0, miner_account, test_generator::bf_major_ver, BLOCK_MAJOR_VERSION_2); + if (!adjustParentBlockSize(blk_1, 2 * 1024 + 1)) { + return false; + } + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + + +namespace +{ + template + bool GenerateAndMutateBlockV2(const cryptonote::Currency& currency, std::vector& events, const std::string& callback, MutateFunc mf) { + TestGenerator bg(currency, events); + + cryptonote::Block blk_1; + bg.generator.constructBlockManually( + blk_1, bg.lastBlock, bg.minerAccount, test_generator::bf_major_ver, BLOCK_MAJOR_VERSION_2); + + mf(blk_1); + + events.push_back(blk_1); + bg.addCallback(callback); + + return true; + } +} + +bool TestBlock2ExtraEmpty::generate(std::vector& events) const { + return GenerateAndMutateBlockV2(m_currency, events, "check_block_purged", [](cryptonote::Block& blk) { + blk.parentBlock.minerTx.extra.clear(); + }); +} + +bool TestBlock2ExtraWithoutMMTag::generate(std::vector& events) const { + return GenerateAndMutateBlockV2(m_currency, events, "check_block_purged", [](cryptonote::Block& blk) { + blk.parentBlock.minerTx.extra.clear(); + cryptonote::add_extra_nonce_to_tx_extra(blk.parentBlock.minerTx.extra, "0xdeadbeef"); + }); +} + +bool TestBlock2ExtraWithGarbage::generate(std::vector& events) const { + return GenerateAndMutateBlockV2(m_currency, events, "check_block_accepted", [](cryptonote::Block& blk) { + cryptonote::add_extra_nonce_to_tx_extra(blk.parentBlock.minerTx.extra, "0xdeadbeef"); + blk.parentBlock.minerTx.extra.push_back(0xde); + blk.parentBlock.minerTx.extra.push_back(0xad); + blk.parentBlock.minerTx.extra.push_back(0xbe); + blk.parentBlock.minerTx.extra.push_back(0xef); + }); +} diff --git a/tests/core_tests/block_validation.h b/tests/core_tests/block_validation.h index ef12d4b6..351b30b0 100644 --- a/tests/core_tests/block_validation.h +++ b/tests/core_tests/block_validation.h @@ -16,180 +16,345 @@ // along with Bytecoin. If not, see . #pragma once + #include "chaingen.h" -template -class gen_block_verification_base : public test_chain_unit_base -{ +const uint64_t UNDEF_HEIGHT = static_cast(cryptonote::UpgradeDetectorBase::UNDEF_HEIGHT); + +class CheckBlockPurged : public test_chain_unit_base { public: - gen_block_verification_base() - { - REGISTER_CALLBACK("check_block_purged", gen_block_verification_base::check_block_purged); + CheckBlockPurged(size_t invalidBlockIdx, uint8_t blockMajorVersion) : + m_invalidBlockIdx(invalidBlockIdx), + m_blockMajorVersion(blockMajorVersion) { + assert(blockMajorVersion == cryptonote::BLOCK_MAJOR_VERSION_1 || blockMajorVersion == cryptonote::BLOCK_MAJOR_VERSION_2); + + cryptonote::CurrencyBuilder currencyBuilder; + currencyBuilder.upgradeHeight(blockMajorVersion == cryptonote::BLOCK_MAJOR_VERSION_1 ? UNDEF_HEIGHT : UINT64_C(0)); + m_currency = currencyBuilder.currency(); + + REGISTER_CALLBACK("check_block_purged", CheckBlockPurged::check_block_purged); + REGISTER_CALLBACK("markInvalidBlock", CheckBlockPurged::markInvalidBlock); } - bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*blk*/) - { - if (invalid_block_idx == event_idx) + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t eventIdx, const cryptonote::Block& /*blk*/) { + if (m_invalidBlockIdx == eventIdx) { return bvc.m_verifivation_failed; - else + } else { return !bvc.m_verifivation_failed; + } } - bool check_block_purged(cryptonote::core& c, size_t ev_index, const std::vector& events) - { - DEFINE_TESTS_ERROR_CONTEXT("gen_block_verification_base::check_block_purged"); + bool check_block_purged(cryptonote::core& c, size_t eventIdx, const std::vector& events) { + DEFINE_TESTS_ERROR_CONTEXT("CheckBlockPurged::check_block_purged"); - CHECK_TEST_CONDITION(invalid_block_idx < ev_index); + CHECK_TEST_CONDITION(m_invalidBlockIdx < eventIdx); CHECK_EQ(0, c.get_pool_transactions_count()); - CHECK_EQ(invalid_block_idx, c.get_current_blockchain_height()); + CHECK_EQ(m_invalidBlockIdx, c.get_current_blockchain_height()); return true; } -}; -template -struct gen_block_accepted_base : public test_chain_unit_base -{ - gen_block_accepted_base() - { - REGISTER_CALLBACK("check_block_accepted", gen_block_accepted_base::check_block_accepted); + bool markInvalidBlock(cryptonote::core& c, size_t eventIdx, const std::vector& events) { + m_invalidBlockIdx = eventIdx + 1; + return true; } - bool check_block_accepted(cryptonote::core& c, size_t /*ev_index*/, const std::vector& /*events*/) - { - DEFINE_TESTS_ERROR_CONTEXT("gen_block_accepted_base::check_block_accepted"); +protected: + size_t m_invalidBlockIdx; + const uint8_t m_blockMajorVersion; +}; + + +struct CheckBlockAccepted : public test_chain_unit_base { + CheckBlockAccepted(size_t expectedBlockchainHeight, uint8_t blockMajorVersion) : + m_expectedBlockchainHeight(expectedBlockchainHeight), + m_blockMajorVersion(blockMajorVersion) { + assert(blockMajorVersion == cryptonote::BLOCK_MAJOR_VERSION_1 || blockMajorVersion == cryptonote::BLOCK_MAJOR_VERSION_2); + + cryptonote::CurrencyBuilder currencyBuilder; + currencyBuilder.upgradeHeight(blockMajorVersion == cryptonote::BLOCK_MAJOR_VERSION_1 ? UNDEF_HEIGHT : UINT64_C(0)); + m_currency = currencyBuilder.currency(); + + REGISTER_CALLBACK("check_block_accepted", CheckBlockAccepted::check_block_accepted); + } + + bool check_block_accepted(cryptonote::core& c, size_t /*eventIdx*/, const std::vector& /*events*/) { + DEFINE_TESTS_ERROR_CONTEXT("CheckBlockAccepted::check_block_accepted"); CHECK_EQ(0, c.get_pool_transactions_count()); - CHECK_EQ(expected_blockchain_height, c.get_current_blockchain_height()); + CHECK_EQ(m_expectedBlockchainHeight, c.get_current_blockchain_height()); return true; } + +protected: + size_t m_expectedBlockchainHeight; + const uint8_t m_blockMajorVersion; }; -struct gen_block_big_major_version : public gen_block_verification_base<1> -{ + +struct TestBlockMajorVersionAccepted : public CheckBlockAccepted { + TestBlockMajorVersionAccepted(uint8_t blockMajorVersion) : + CheckBlockAccepted(2, blockMajorVersion) {} + bool generate(std::vector& events) const; }; -struct gen_block_big_minor_version : public gen_block_accepted_base<2> -{ +struct TestBlockMajorVersionRejected : public CheckBlockPurged { + TestBlockMajorVersionRejected(uint8_t blockAcceptedVersion, uint8_t blockGeneratedVersion) : + CheckBlockPurged(1, blockAcceptedVersion), m_blockGeneratedVersion(blockGeneratedVersion) {} + + const uint8_t m_blockGeneratedVersion; + bool generate(std::vector& events) const; }; -struct gen_block_ts_not_checked : public gen_block_accepted_base -{ +struct TestBlockBigMinorVersion : public CheckBlockAccepted { + + TestBlockBigMinorVersion(uint8_t blockMajorVersion) + : CheckBlockAccepted(2, blockMajorVersion) {} + bool generate(std::vector& events) const; }; -struct gen_block_ts_in_past : public gen_block_verification_base +struct gen_block_ts_not_checked : public CheckBlockAccepted { + gen_block_ts_not_checked(uint8_t blockMajorVersion) + : CheckBlockAccepted(0, blockMajorVersion) { + m_expectedBlockchainHeight = m_currency.timestampCheckWindow(); + } + bool generate(std::vector& events) const; }; -struct gen_block_ts_in_future : public gen_block_verification_base<1> +struct gen_block_ts_in_past : public CheckBlockPurged { + gen_block_ts_in_past(uint8_t blockMajorVersion) + : CheckBlockPurged(0, blockMajorVersion) { + m_invalidBlockIdx = m_currency.timestampCheckWindow(); + } + bool generate(std::vector& events) const; }; -struct gen_block_invalid_prev_id : public gen_block_verification_base<1> +struct gen_block_ts_in_future_rejected : public CheckBlockPurged { - bool generate(std::vector& events) const; - bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*blk*/); -}; + gen_block_ts_in_future_rejected(uint8_t blockMajorVersion) + : CheckBlockPurged(1, blockMajorVersion) {} -struct gen_block_invalid_nonce : public gen_block_verification_base<3> -{ bool generate(std::vector& events) const; }; -struct gen_block_no_miner_tx : public gen_block_verification_base<1> +struct gen_block_ts_in_future_accepted : public CheckBlockAccepted { + gen_block_ts_in_future_accepted(uint8_t blockMajorVersion) + : CheckBlockAccepted(2, blockMajorVersion) {} + bool generate(std::vector& events) const; }; -struct gen_block_unlock_time_is_low : public gen_block_verification_base<1> +struct gen_block_invalid_prev_id : public CheckBlockPurged { + gen_block_invalid_prev_id(uint8_t blockMajorVersion) + : CheckBlockPurged(1, blockMajorVersion) {} + + bool generate(std::vector& events) const; + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::Block& /*blk*/); +}; + +struct gen_block_invalid_nonce : public CheckBlockPurged +{ + gen_block_invalid_nonce(uint8_t blockMajorVersion) + : CheckBlockPurged(3, blockMajorVersion) {} + bool generate(std::vector& events) const; }; -struct gen_block_unlock_time_is_high : public gen_block_verification_base<1> +struct gen_block_no_miner_tx : public CheckBlockPurged { + gen_block_no_miner_tx(uint8_t blockMajorVersion) + : CheckBlockPurged(1, blockMajorVersion) {} + bool generate(std::vector& events) const; }; -struct gen_block_unlock_time_is_timestamp_in_past : public gen_block_verification_base<1> +struct gen_block_unlock_time_is_low : public CheckBlockPurged { + gen_block_unlock_time_is_low(uint8_t blockMajorVersion) + : CheckBlockPurged(1, blockMajorVersion) {} + bool generate(std::vector& events) const; }; -struct gen_block_unlock_time_is_timestamp_in_future : public gen_block_verification_base<1> +struct gen_block_unlock_time_is_high : public CheckBlockPurged { + gen_block_unlock_time_is_high(uint8_t blockMajorVersion) + : CheckBlockPurged(1, blockMajorVersion) {} + bool generate(std::vector& events) const; }; -struct gen_block_height_is_low : public gen_block_verification_base<1> +struct gen_block_unlock_time_is_timestamp_in_past : public CheckBlockPurged { + gen_block_unlock_time_is_timestamp_in_past(uint8_t blockMajorVersion) + : CheckBlockPurged(1, blockMajorVersion) {} + bool generate(std::vector& events) const; }; -struct gen_block_height_is_high : public gen_block_verification_base<1> +struct gen_block_unlock_time_is_timestamp_in_future : public CheckBlockPurged { + gen_block_unlock_time_is_timestamp_in_future(uint8_t blockMajorVersion) + : CheckBlockPurged(1, blockMajorVersion) {} + bool generate(std::vector& events) const; }; -struct gen_block_miner_tx_has_2_tx_gen_in : public gen_block_verification_base<1> +struct gen_block_height_is_low : public CheckBlockPurged { + gen_block_height_is_low(uint8_t blockMajorVersion) + : CheckBlockPurged(1, blockMajorVersion) {} + bool generate(std::vector& events) const; }; -struct gen_block_miner_tx_has_2_in : public gen_block_verification_base +struct gen_block_height_is_high : public CheckBlockPurged { + gen_block_height_is_high(uint8_t blockMajorVersion) + : CheckBlockPurged(1, blockMajorVersion) {} + bool generate(std::vector& events) const; }; -struct gen_block_miner_tx_with_txin_to_key : public gen_block_verification_base +struct gen_block_miner_tx_has_2_tx_gen_in : public CheckBlockPurged { + gen_block_miner_tx_has_2_tx_gen_in(uint8_t blockMajorVersion) + : CheckBlockPurged(1, blockMajorVersion) {} + bool generate(std::vector& events) const; }; -struct gen_block_miner_tx_out_is_small : public gen_block_verification_base<1> +struct gen_block_miner_tx_has_2_in : public CheckBlockPurged { + gen_block_miner_tx_has_2_in(uint8_t blockMajorVersion) + : CheckBlockPurged(0, blockMajorVersion) { + m_invalidBlockIdx = m_currency.minedMoneyUnlockWindow() + 1; + } + bool generate(std::vector& events) const; }; -struct gen_block_miner_tx_out_is_big : public gen_block_verification_base<1> +struct gen_block_miner_tx_with_txin_to_key : public CheckBlockPurged { + gen_block_miner_tx_with_txin_to_key(uint8_t blockMajorVersion) + : CheckBlockPurged(0, blockMajorVersion) { + m_invalidBlockIdx = m_currency.minedMoneyUnlockWindow() + 2; + } + bool generate(std::vector& events) const; }; -struct gen_block_miner_tx_has_no_out : public gen_block_verification_base<1> +struct gen_block_miner_tx_out_is_small : public CheckBlockPurged { + gen_block_miner_tx_out_is_small(uint8_t blockMajorVersion) + : CheckBlockPurged(1, blockMajorVersion) {} + bool generate(std::vector& events) const; }; -struct gen_block_miner_tx_has_out_to_alice : public gen_block_accepted_base<2> +struct gen_block_miner_tx_out_is_big : public CheckBlockPurged { + gen_block_miner_tx_out_is_big(uint8_t blockMajorVersion) + : CheckBlockPurged(1, blockMajorVersion) {} + bool generate(std::vector& events) const; }; -struct gen_block_has_invalid_tx : public gen_block_verification_base<1> +struct gen_block_miner_tx_has_no_out : public CheckBlockPurged { + gen_block_miner_tx_has_no_out(uint8_t blockMajorVersion) + : CheckBlockPurged(1, blockMajorVersion) {} + bool generate(std::vector& events) const; }; -struct gen_block_is_too_big : public gen_block_verification_base<1> +struct gen_block_miner_tx_has_out_to_alice : public CheckBlockAccepted { + gen_block_miner_tx_has_out_to_alice(uint8_t blockMajorVersion) + : CheckBlockAccepted(2, blockMajorVersion) {} + + bool generate(std::vector& events) const; +}; + +struct gen_block_has_invalid_tx : public CheckBlockPurged +{ + gen_block_has_invalid_tx(uint8_t blockMajorVersion) + : CheckBlockPurged(1, blockMajorVersion) {} + + bool generate(std::vector& events) const; +}; + +struct gen_block_is_too_big : public CheckBlockPurged +{ + gen_block_is_too_big(uint8_t blockMajorVersion) + : CheckBlockPurged(1, blockMajorVersion) {} + + bool generate(std::vector& events) const; +}; + +struct TestBlockCumulativeSizeExceedsLimit : public CheckBlockPurged { + TestBlockCumulativeSizeExceedsLimit(uint8_t blockMajorVersion) + : CheckBlockPurged(std::numeric_limits::max(), blockMajorVersion) { + } + bool generate(std::vector& events) const; }; struct gen_block_invalid_binary_format : public test_chain_unit_base { - gen_block_invalid_binary_format(); + gen_block_invalid_binary_format(uint8_t blockMajorVersion); + bool generate(std::vector& events) const; - bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*blk*/); + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::Block& /*blk*/); bool check_all_blocks_purged(cryptonote::core& c, size_t ev_index, const std::vector& events); bool corrupt_blocks_boundary(cryptonote::core& c, size_t ev_index, const std::vector& events); private: + const uint8_t m_blockMajorVersion; size_t m_corrupt_blocks_begin_idx; }; + +struct TestMaxSizeOfParentBlock : public CheckBlockAccepted { + TestMaxSizeOfParentBlock() : CheckBlockAccepted(2, cryptonote::BLOCK_MAJOR_VERSION_2) { + } + + bool generate(std::vector& events) const; +}; + +struct TestBigParentBlock : public CheckBlockPurged { + TestBigParentBlock() : CheckBlockPurged(1, cryptonote::BLOCK_MAJOR_VERSION_2) { + } + + bool generate(std::vector& events) const; +}; + +struct TestBlock2ExtraEmpty : public CheckBlockPurged { + + TestBlock2ExtraEmpty() : CheckBlockPurged(1, cryptonote::BLOCK_MAJOR_VERSION_2) {} + + bool generate(std::vector& events) const; +}; + +struct TestBlock2ExtraWithoutMMTag : public CheckBlockPurged { + + TestBlock2ExtraWithoutMMTag() : CheckBlockPurged(1, cryptonote::BLOCK_MAJOR_VERSION_2) {} + + bool generate(std::vector& events) const; +}; + +struct TestBlock2ExtraWithGarbage : public CheckBlockAccepted { + + TestBlock2ExtraWithGarbage() : CheckBlockAccepted(2, cryptonote::BLOCK_MAJOR_VERSION_2) {} + + bool generate(std::vector& events) const; +}; diff --git a/tests/core_tests/chain_split_1.cpp b/tests/core_tests/chain_split_1.cpp index 082968b6..28719f42 100644 --- a/tests/core_tests/chain_split_1.cpp +++ b/tests/core_tests/chain_split_1.cpp @@ -15,8 +15,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . -#include "chaingen.h" -#include "chaingen_tests_list.h" +#include "chain_split_1.h" using namespace std; @@ -167,7 +166,7 @@ bool gen_simple_chain_split_1::check_split_not_switched(cryptonote::core& c, siz //check height CHECK_TEST_CONDITION(c.get_current_blockchain_height() == 9); CHECK_TEST_CONDITION(c.get_blockchain_total_transactions() == 9); - CHECK_TEST_CONDITION(c.get_tail_id() == get_block_hash(boost::get(events[8]))); + CHECK_TEST_CONDITION(c.get_tail_id() == get_block_hash(boost::get(events[8]))); CHECK_TEST_CONDITION(c.get_alternative_blocks_count() == 2); return true; } @@ -178,7 +177,7 @@ bool gen_simple_chain_split_1::check_split_not_switched2(cryptonote::core& c, si //check height CHECK_TEST_CONDITION(c.get_current_blockchain_height() == 9); CHECK_TEST_CONDITION(c.get_blockchain_total_transactions() == 9); - CHECK_TEST_CONDITION(c.get_tail_id() == get_block_hash(boost::get(events[8]))); + CHECK_TEST_CONDITION(c.get_tail_id() == get_block_hash(boost::get(events[8]))); CHECK_TEST_CONDITION(c.get_alternative_blocks_count() == 3); return true; } @@ -190,7 +189,7 @@ bool gen_simple_chain_split_1::check_split_switched(cryptonote::core& c, size_t //check height CHECK_TEST_CONDITION(c.get_current_blockchain_height() == 10); CHECK_TEST_CONDITION(c.get_blockchain_total_transactions() == 10); - CHECK_TEST_CONDITION(c.get_tail_id() == get_block_hash(boost::get(events[14]))); + CHECK_TEST_CONDITION(c.get_tail_id() == get_block_hash(boost::get(events[14]))); CHECK_TEST_CONDITION(c.get_alternative_blocks_count() == 3); return true; } @@ -201,7 +200,7 @@ bool gen_simple_chain_split_1::check_split_not_switched_back(cryptonote::core& c //check height CHECK_TEST_CONDITION(c.get_current_blockchain_height() == 14); CHECK_TEST_CONDITION(c.get_blockchain_total_transactions() == 14); - CHECK_TEST_CONDITION(c.get_tail_id() == get_block_hash(boost::get(events[19]))); + CHECK_TEST_CONDITION(c.get_tail_id() == get_block_hash(boost::get(events[19]))); CHECK_TEST_CONDITION(c.get_alternative_blocks_count() == 8); return true; @@ -214,7 +213,7 @@ bool gen_simple_chain_split_1::check_split_switched_back_1(cryptonote::core& c, //check height CHECK_TEST_CONDITION(c.get_current_blockchain_height()== 15); CHECK_TEST_CONDITION(c.get_blockchain_total_transactions() == 15); - CHECK_TEST_CONDITION(c.get_tail_id() == get_block_hash(boost::get(events[26]))); + CHECK_TEST_CONDITION(c.get_tail_id() == get_block_hash(boost::get(events[26]))); CHECK_TEST_CONDITION(c.get_alternative_blocks_count() == 8); return true; @@ -226,7 +225,7 @@ bool gen_simple_chain_split_1::check_split_switched_back_2(cryptonote::core& c, //check height CHECK_TEST_CONDITION(c.get_current_blockchain_height() == 16); CHECK_TEST_CONDITION(c.get_blockchain_total_transactions() == 16); - CHECK_TEST_CONDITION(c.get_tail_id() == get_block_hash(boost::get(events[28]))); + CHECK_TEST_CONDITION(c.get_tail_id() == get_block_hash(boost::get(events[28]))); CHECK_TEST_CONDITION(c.get_alternative_blocks_count() == 8); return true; } diff --git a/tests/core_tests/chain_switch_1.cpp b/tests/core_tests/chain_switch_1.cpp index 15e12dd8..c82b8654 100644 --- a/tests/core_tests/chain_switch_1.cpp +++ b/tests/core_tests/chain_switch_1.cpp @@ -15,8 +15,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . -#include "chaingen.h" -#include "chaingen_tests_list.h" +#include "chain_switch_1.h" using namespace epee; using namespace cryptonote; @@ -68,7 +67,7 @@ bool gen_chain_switch_1::generate(std::vector& events) const MAKE_TX_LIST_START(events, txs_blk_3, miner_account, recipient_account_2, MK_COINS(7), blk_2); // 8 + 2N MAKE_TX_LIST_START(events, txs_blk_4, miner_account, recipient_account_3, MK_COINS(11), blk_2); // 9 + 2N MAKE_TX_LIST_START(events, txs_blk_5, miner_account, recipient_account_4, MK_COINS(13), blk_2); // 10 + 2N - std::list txs_blk_6; + std::list txs_blk_6; txs_blk_6.push_back(txs_blk_4.front()); // Transactions, that has different order in alt block chains @@ -115,15 +114,15 @@ bool gen_chain_switch_1::check_split_not_switched(cryptonote::core& c, size_t ev m_recipient_account_3 = boost::get(events[3]); m_recipient_account_4 = boost::get(events[4]); - std::list blocks; + std::list blocks; bool r = c.get_blocks(0, 10000, blocks); CHECK_TEST_CONDITION(r); - CHECK_EQ(5 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks.size()); - CHECK_TEST_CONDITION(blocks.back() == boost::get(events[20 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW])); // blk_4 + CHECK_EQ(5 + 2 * m_currency.minedMoneyUnlockWindow(), blocks.size()); + CHECK_TEST_CONDITION(blocks.back() == boost::get(events[20 + 2 * m_currency.minedMoneyUnlockWindow()])); // blk_4 CHECK_EQ(2, c.get_alternative_blocks_count()); - std::vector chain; + std::vector chain; map_hash2tx_t mtx; r = find_block_chain(events, chain, mtx, get_block_hash(blocks.back())); CHECK_TEST_CONDITION(r); @@ -132,7 +131,7 @@ bool gen_chain_switch_1::check_split_not_switched(cryptonote::core& c, size_t ev CHECK_EQ(MK_COINS(14), get_balance(m_recipient_account_3, chain, mtx)); CHECK_EQ(MK_COINS(3), get_balance(m_recipient_account_4, chain, mtx)); - std::list tx_pool; + std::list tx_pool; c.get_pool_transactions(tx_pool); CHECK_EQ(1, tx_pool.size()); @@ -152,27 +151,27 @@ bool gen_chain_switch_1::check_split_switched(cryptonote::core& c, size_t ev_ind { DEFINE_TESTS_ERROR_CONTEXT("gen_chain_switch_1::check_split_switched"); - std::list blocks; + std::list blocks; bool r = c.get_blocks(0, 10000, blocks); CHECK_TEST_CONDITION(r); - CHECK_EQ(6 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks.size()); + CHECK_EQ(6 + 2 * m_currency.minedMoneyUnlockWindow(), blocks.size()); auto it = blocks.end(); --it; --it; --it; CHECK_TEST_CONDITION(std::equal(blocks.begin(), it, m_chain_1.begin())); - CHECK_TEST_CONDITION(blocks.back() == boost::get(events[24 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW])); // blk_7 + CHECK_TEST_CONDITION(blocks.back() == boost::get(events[24 + 2 * m_currency.minedMoneyUnlockWindow()])); // blk_7 - std::list alt_blocks; + std::list alt_blocks; r = c.get_alternative_blocks(alt_blocks); CHECK_TEST_CONDITION(r); CHECK_EQ(2, c.get_alternative_blocks_count()); // Some blocks that were in main chain are in alt chain now - BOOST_FOREACH(block b, alt_blocks) + BOOST_FOREACH(Block b, alt_blocks) { CHECK_TEST_CONDITION(m_chain_1.end() != std::find(m_chain_1.begin(), m_chain_1.end(), b)); } - std::vector chain; + std::vector chain; map_hash2tx_t mtx; r = find_block_chain(events, chain, mtx, get_block_hash(blocks.back())); CHECK_TEST_CONDITION(r); @@ -181,7 +180,7 @@ bool gen_chain_switch_1::check_split_switched(cryptonote::core& c, size_t ev_ind CHECK_EQ(MK_COINS(14), get_balance(m_recipient_account_3, chain, mtx)); CHECK_EQ(MK_COINS(16), get_balance(m_recipient_account_4, chain, mtx)); - std::list tx_pool; + std::list tx_pool; c.get_pool_transactions(tx_pool); CHECK_EQ(1, tx_pool.size()); CHECK_TEST_CONDITION(!(tx_pool.front() == m_tx_pool.front())); diff --git a/tests/core_tests/chain_switch_1.h b/tests/core_tests/chain_switch_1.h index cf3a39d4..6a467543 100644 --- a/tests/core_tests/chain_switch_1.h +++ b/tests/core_tests/chain_switch_1.h @@ -32,12 +32,12 @@ public: bool check_split_switched(cryptonote::core& c, size_t ev_index, const std::vector& events); private: - std::list m_chain_1; + std::list m_chain_1; cryptonote::account_base m_recipient_account_1; cryptonote::account_base m_recipient_account_2; cryptonote::account_base m_recipient_account_3; cryptonote::account_base m_recipient_account_4; - std::list m_tx_pool; + std::list m_tx_pool; }; diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 16717135..58ba58b6 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -15,21 +15,28 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . +#include "chaingen.h" + #include #include -#include +#include + +#include +#include +#include #include "include_base_utils.h" +#include "misc_language.h" -#include "console_handler.h" - -#include "p2p/net_node.h" +#include "common/command_line.h" +#include "cryptonote_core/account_boost_serialization.h" #include "cryptonote_core/cryptonote_basic.h" #include "cryptonote_core/cryptonote_basic_impl.h" #include "cryptonote_core/cryptonote_format_utils.h" -#include "cryptonote_core/miner.h" - -#include "chaingen.h" +#include "cryptonote_core/cryptonote_core.h" +#include "cryptonote_core/cryptonote_boost_serialization.h" +#include "cryptonote_core/Currency.h" +#include "cryptonote_core/UpgradeDetector.h" using namespace std; @@ -37,226 +44,18 @@ using namespace epee; using namespace cryptonote; -void test_generator::get_block_chain(std::vector& blockchain, const crypto::hash& head, size_t n) const -{ - crypto::hash curr = head; - while (null_hash != curr && blockchain.size() < n) - { - auto it = m_blocks_info.find(curr); - if (m_blocks_info.end() == it) - { - throw std::runtime_error("block hash wasn't found"); - } - - blockchain.push_back(it->second); - curr = it->second.prev_id; - } - - std::reverse(blockchain.begin(), blockchain.end()); -} - -void test_generator::get_last_n_block_sizes(std::vector& block_sizes, const crypto::hash& head, size_t n) const -{ - std::vector blockchain; - get_block_chain(blockchain, head, n); - BOOST_FOREACH(auto& bi, blockchain) - { - block_sizes.push_back(bi.block_size); - } -} - -uint64_t test_generator::get_already_generated_coins(const crypto::hash& blk_id) const -{ - auto it = m_blocks_info.find(blk_id); - if (it == m_blocks_info.end()) - throw std::runtime_error("block hash wasn't found"); - - return it->second.already_generated_coins; -} - -uint64_t test_generator::get_already_generated_coins(const cryptonote::block& blk) const -{ - crypto::hash blk_hash; - get_block_hash(blk, blk_hash); - return get_already_generated_coins(blk_hash); -} - -void test_generator::add_block(const cryptonote::block& blk, size_t tsx_size, std::vector& block_sizes, uint64_t already_generated_coins) -{ - const size_t block_size = tsx_size + get_object_blobsize(blk.miner_tx); - uint64_t block_reward; - get_block_reward(misc_utils::median(block_sizes), block_size, already_generated_coins, block_reward); - m_blocks_info[get_block_hash(blk)] = block_info(blk.prev_id, already_generated_coins + block_reward, block_size); -} - -bool test_generator::construct_block(cryptonote::block& blk, uint64_t height, const crypto::hash& prev_id, - const cryptonote::account_base& miner_acc, uint64_t timestamp, uint64_t already_generated_coins, - std::vector& block_sizes, const std::list& tx_list) -{ - blk.major_version = CURRENT_BLOCK_MAJOR_VERSION; - blk.minor_version = CURRENT_BLOCK_MINOR_VERSION; - blk.timestamp = timestamp; - blk.prev_id = prev_id; - - blk.tx_hashes.reserve(tx_list.size()); - BOOST_FOREACH(const transaction &tx, tx_list) - { - crypto::hash tx_hash; - get_transaction_hash(tx, tx_hash); - blk.tx_hashes.push_back(tx_hash); - } - - uint64_t total_fee = 0; - size_t txs_size = 0; - BOOST_FOREACH(auto& tx, tx_list) - { - uint64_t fee = 0; - bool r = get_tx_fee(tx, fee); - CHECK_AND_ASSERT_MES(r, false, "wrong transaction passed to construct_block"); - total_fee += fee; - txs_size += get_object_blobsize(tx); - } - - blk.miner_tx = AUTO_VAL_INIT(blk.miner_tx); - size_t target_block_size = txs_size + get_object_blobsize(blk.miner_tx); - while (true) - { - if (!construct_miner_tx(height, misc_utils::median(block_sizes), already_generated_coins, target_block_size, total_fee, miner_acc.get_keys().m_account_address, blk.miner_tx, blobdata(), 10)) - return false; - - size_t actual_block_size = txs_size + get_object_blobsize(blk.miner_tx); - if (target_block_size < actual_block_size) - { - target_block_size = actual_block_size; - } - else if (actual_block_size < target_block_size) - { - size_t delta = target_block_size - actual_block_size; - blk.miner_tx.extra.resize(blk.miner_tx.extra.size() + delta, 0); - actual_block_size = txs_size + get_object_blobsize(blk.miner_tx); - if (actual_block_size == target_block_size) - { - break; - } - else - { - CHECK_AND_ASSERT_MES(target_block_size < actual_block_size, false, "Unexpected block size"); - delta = actual_block_size - target_block_size; - blk.miner_tx.extra.resize(blk.miner_tx.extra.size() - delta); - actual_block_size = txs_size + get_object_blobsize(blk.miner_tx); - if (actual_block_size == target_block_size) - { - break; - } - else - { - CHECK_AND_ASSERT_MES(actual_block_size < target_block_size, false, "Unexpected block size"); - blk.miner_tx.extra.resize(blk.miner_tx.extra.size() + delta, 0); - target_block_size = txs_size + get_object_blobsize(blk.miner_tx); - } - } - } - else - { - break; - } - } - - //blk.tree_root_hash = get_tx_tree_hash(blk); - - // Nonce search... - blk.nonce = 0; - crypto::cn_context context; - while (!miner::find_nonce_for_given_block(context, blk, get_test_difficulty(), height)) - blk.timestamp++; - - add_block(blk, txs_size, block_sizes, already_generated_coins); - - return true; -} - -bool test_generator::construct_block(cryptonote::block& blk, const cryptonote::account_base& miner_acc, uint64_t timestamp) -{ - std::vector block_sizes; - std::list tx_list; - return construct_block(blk, 0, null_hash, miner_acc, timestamp, 0, block_sizes, tx_list); -} - -bool test_generator::construct_block(cryptonote::block& blk, const cryptonote::block& blk_prev, - const cryptonote::account_base& miner_acc, - const std::list& tx_list/* = std::list()*/) -{ - uint64_t height = boost::get(blk_prev.miner_tx.vin.front()).height + 1; - crypto::hash prev_id = get_block_hash(blk_prev); - // Keep difficulty unchanged - uint64_t timestamp = blk_prev.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN; - uint64_t already_generated_coins = get_already_generated_coins(prev_id); - std::vector block_sizes; - get_last_n_block_sizes(block_sizes, prev_id, CRYPTONOTE_REWARD_BLOCKS_WINDOW); - - return construct_block(blk, height, prev_id, miner_acc, timestamp, already_generated_coins, block_sizes, tx_list); -} - -bool test_generator::construct_block_manually(block& blk, const block& prev_block, const account_base& miner_acc, - int actual_params/* = bf_none*/, uint8_t major_ver/* = 0*/, - uint8_t minor_ver/* = 0*/, uint64_t timestamp/* = 0*/, - const crypto::hash& prev_id/* = crypto::hash()*/, const difficulty_type& diffic/* = 1*/, - const transaction& miner_tx/* = transaction()*/, - const std::vector& tx_hashes/* = std::vector()*/, - size_t txs_sizes/* = 0*/) -{ - blk.major_version = actual_params & bf_major_ver ? major_ver : CURRENT_BLOCK_MAJOR_VERSION; - blk.minor_version = actual_params & bf_minor_ver ? minor_ver : CURRENT_BLOCK_MINOR_VERSION; - blk.timestamp = actual_params & bf_timestamp ? timestamp : prev_block.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN; // Keep difficulty unchanged - blk.prev_id = actual_params & bf_prev_id ? prev_id : get_block_hash(prev_block); - blk.tx_hashes = actual_params & bf_tx_hashes ? tx_hashes : std::vector(); - - size_t height = get_block_height(prev_block) + 1; - uint64_t already_generated_coins = get_already_generated_coins(prev_block); - std::vector block_sizes; - get_last_n_block_sizes(block_sizes, get_block_hash(prev_block), CRYPTONOTE_REWARD_BLOCKS_WINDOW); - if (actual_params & bf_miner_tx) - { - blk.miner_tx = miner_tx; - } - else - { - size_t current_block_size = txs_sizes + get_object_blobsize(blk.miner_tx); - // TODO: This will work, until size of constructed block is less then CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE - if (!construct_miner_tx(height, misc_utils::median(block_sizes), already_generated_coins, current_block_size, 0, miner_acc.get_keys().m_account_address, blk.miner_tx, blobdata(), 1)) - return false; - } - - //blk.tree_root_hash = get_tx_tree_hash(blk); - - difficulty_type a_diffic = actual_params & bf_diffic ? diffic : get_test_difficulty(); - fill_nonce(blk, a_diffic, height); - - add_block(blk, txs_sizes, block_sizes, already_generated_coins); - - return true; -} - -bool test_generator::construct_block_manually_tx(cryptonote::block& blk, const cryptonote::block& prev_block, - const cryptonote::account_base& miner_acc, - const std::vector& tx_hashes, size_t txs_size) -{ - return construct_block_manually(blk, prev_block, miner_acc, bf_tx_hashes, 0, 0, 0, crypto::hash(), 0, transaction(), tx_hashes, txs_size); -} - - struct output_index { - const cryptonote::txout_target_v out; + const cryptonote::TransactionOutputTarget out; uint64_t amount; size_t blk_height; // block height size_t tx_no; // index of transaction in block size_t out_no; // index of out in transaction size_t idx; bool spent; - const cryptonote::block *p_blk; - const cryptonote::transaction *p_tx; + const cryptonote::Block *p_blk; + const cryptonote::Transaction *p_tx; - output_index(const cryptonote::txout_target_v &_out, uint64_t _a, size_t _h, size_t tno, size_t ono, const cryptonote::block *_pb, const cryptonote::transaction *_pt) + output_index(const cryptonote::TransactionOutputTarget &_out, uint64_t _a, size_t _h, size_t tno, size_t ono, const cryptonote::Block *_pb, const cryptonote::Transaction *_pt) : out(_out), amount(_a), blk_height(_h), tx_no(tno), out_no(ono), idx(0), spent(false), p_blk(_pb), p_tx(_pt) { } output_index(const output_index &other) @@ -301,13 +100,13 @@ namespace } } -bool init_output_indices(map_output_idx_t& outs, std::map >& outs_mine, const std::vector& blockchain, const map_hash2tx_t& mtx, const cryptonote::account_base& from) { +bool init_output_indices(map_output_idx_t& outs, std::map >& outs_mine, const std::vector& blockchain, const map_hash2tx_t& mtx, const cryptonote::account_base& from) { - BOOST_FOREACH (const block& blk, blockchain) { - vector vtx; - vtx.push_back(&blk.miner_tx); + BOOST_FOREACH (const Block& blk, blockchain) { + vector vtx; + vtx.push_back(&blk.minerTx); - BOOST_FOREACH(const crypto::hash &h, blk.tx_hashes) { + for (const crypto::hash& h : blk.txHashes) { const map_hash2tx_t::const_iterator cit = mtx.find(h); if (mtx.end() == cit) throw std::runtime_error("block contains an unknown tx hash"); @@ -318,22 +117,25 @@ bool init_output_indices(map_output_idx_t& outs, std::map(*blk.miner_tx.vin.begin()).height, i, j, &blk, vtx[i]); - - if (2 == out.target.which()) { // out_to_key - outs[out.amount].push_back(oi); - size_t tx_global_idx = outs[out.amount].size() - 1; - outs[out.amount][tx_global_idx].idx = tx_global_idx; - // Is out to me? - if (is_out_to_acc(from.get_keys(), boost::get(out.target), get_tx_pub_key_from_extra(tx), j)) { - outs_mine[out.amount].push_back(tx_global_idx); - } + const TransactionOutput &out = tx.vout[j]; + if (out.target.type() == typeid(TransactionOutputToKey)) { + output_index oi(out.target, out.amount, boost::get(*blk.minerTx.vin.begin()).height, i, j, &blk, vtx[i]); + outs[out.amount].push_back(oi); + size_t tx_global_idx = outs[out.amount].size() - 1; + outs[out.amount][tx_global_idx].idx = tx_global_idx; + // Is out to me? + if (is_out_to_acc(from.get_keys(), boost::get(out.target), get_tx_pub_key_from_extra(tx), keyIndex)) { + outs_mine[out.amount].push_back(tx_global_idx); } + + ++keyIndex; + } else if (out.target.type() == typeid(TransactionOutputMultisignature)) { + keyIndex += boost::get(out.target).keys.size(); + } } } } @@ -341,24 +143,24 @@ bool init_output_indices(map_output_idx_t& outs, std::map& blockchain, const map_hash2tx_t& mtx, const cryptonote::account_base& from) { +bool init_spent_output_indices(map_output_idx_t& outs, map_output_t& outs_mine, const std::vector& blockchain, const map_hash2tx_t& mtx, const cryptonote::account_base& from) { - BOOST_FOREACH (const map_output_t::value_type &o, outs_mine) { + for (const map_output_t::value_type& o: outs_mine) { for (size_t i = 0; i < o.second.size(); ++i) { output_index &oi = outs[o.first][o.second[i]]; // construct key image for this output crypto::key_image img; - keypair in_ephemeral; + KeyPair in_ephemeral; generate_key_image_helper(from.get_keys(), get_tx_pub_key_from_extra(*oi.p_tx), oi.out_no, in_ephemeral, img); // lookup for this key image in the events vector - BOOST_FOREACH(auto& tx_pair, mtx) { - const transaction& tx = *tx_pair.second; - BOOST_FOREACH(const txin_v &in, tx.vin) { - if (typeid(txin_to_key) == in.type()) { - const txin_to_key &itk = boost::get(in); - if (itk.k_image == img) { + for (auto& tx_pair : mtx) { + const Transaction& tx = *tx_pair.second; + for (const auto& in : tx.vin) { + if (typeid(TransactionInputToKey) == in.type()) { + const TransactionInputToKey &itk = boost::get(in); + if (itk.keyImage == img) { oi.spent = true; } } @@ -398,7 +200,7 @@ bool fill_output_entries(std::vector& out_indices, size_t sender_o if (append) { - const txout_to_key& otk = boost::get(oi.out); + const TransactionOutputToKey& otk = boost::get(oi.out); output_entries.push_back(tx_source_entry::output_entry(oi.idx, otk.key)); } } @@ -407,12 +209,12 @@ bool fill_output_entries(std::vector& out_indices, size_t sender_o } bool fill_tx_sources(std::vector& sources, const std::vector& events, - const block& blk_head, const cryptonote::account_base& from, uint64_t amount, size_t nmix) + const Block& blk_head, const cryptonote::account_base& from, uint64_t amount, size_t nmix) { map_output_idx_t outs; map_output_t outs_mine; - std::vector blockchain; + std::vector blockchain; map_hash2tx_t mtx; if (!find_block_chain(events, blockchain, mtx, get_block_hash(blk_head))) return false; @@ -464,7 +266,7 @@ bool fill_tx_destination(tx_destination_entry &de, const cryptonote::account_bas return true; } -void fill_tx_sources_and_destinations(const std::vector& events, const block& blk_head, +void fill_tx_sources_and_destinations(const std::vector& events, const Block& blk_head, const cryptonote::account_base& from, const cryptonote::account_base& to, uint64_t amount, uint64_t fee, size_t nmix, std::vector& sources, std::vector& destinations) @@ -490,55 +292,7 @@ void fill_tx_sources_and_destinations(const std::vector& event } } -void fill_nonce(cryptonote::block& blk, const difficulty_type& diffic, uint64_t height) -{ - blk.nonce = 0; - crypto::cn_context context; - while (!miner::find_nonce_for_given_block(context, blk, diffic, height)) - blk.timestamp++; -} - -bool construct_miner_tx_manually(size_t height, uint64_t already_generated_coins, - const account_public_address& miner_address, transaction& tx, uint64_t fee, - keypair* p_txkey/* = 0*/) -{ - keypair txkey; - txkey = keypair::generate(); - add_tx_pub_key_to_extra(tx, txkey.pub); - - if (0 != p_txkey) - *p_txkey = txkey; - - txin_gen in; - in.height = height; - tx.vin.push_back(in); - - // This will work, until size of constructed block is less then CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE - uint64_t block_reward; - if (!get_block_reward(0, 0, already_generated_coins, block_reward)) - { - LOG_PRINT_L0("Block is too big"); - return false; - } - block_reward += fee; - - crypto::key_derivation derivation; - crypto::public_key out_eph_public_key; - crypto::generate_key_derivation(miner_address.m_view_public_key, txkey.sec, derivation); - crypto::derive_public_key(derivation, 0, miner_address.m_spend_public_key, out_eph_public_key); - - tx_out out; - out.amount = block_reward; - out.target = txout_to_key(out_eph_public_key); - tx.vout.push_back(out); - - tx.version = CURRENT_TRANSACTION_VERSION; - tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; - - return true; -} - -bool construct_tx_to_key(const std::vector& events, cryptonote::transaction& tx, const block& blk_head, +bool construct_tx_to_key(const std::vector& events, cryptonote::Transaction& tx, const Block& blk_head, const cryptonote::account_base& from, const cryptonote::account_base& to, uint64_t amount, uint64_t fee, size_t nmix) { @@ -549,16 +303,16 @@ bool construct_tx_to_key(const std::vector& events, cryptonote return construct_tx(from.get_keys(), sources, destinations, std::vector(), tx, 0); } -transaction construct_tx_with_fee(std::vector& events, const block& blk_head, +Transaction construct_tx_with_fee(std::vector& events, const Block& blk_head, const account_base& acc_from, const account_base& acc_to, uint64_t amount, uint64_t fee) { - transaction tx; + Transaction tx; construct_tx_to_key(events, tx, blk_head, acc_from, acc_to, amount, fee, 0); events.push_back(tx); return tx; } -uint64_t get_balance(const cryptonote::account_base& addr, const std::vector& blockchain, const map_hash2tx_t& mtx) { +uint64_t get_balance(const cryptonote::account_base& addr, const std::vector& blockchain, const map_hash2tx_t& mtx) { uint64_t res = 0; std::map > outs; std::map > outs_mine; @@ -584,12 +338,12 @@ uint64_t get_balance(const cryptonote::account_base& addr, const std::vector& blockchain, const map_hash2tx_t& mtx, map_hash2tx_t& confirmed_txs) +void get_confirmed_txs(const std::vector& blockchain, const map_hash2tx_t& mtx, map_hash2tx_t& confirmed_txs) { std::unordered_set confirmed_hashes; - BOOST_FOREACH(const block& blk, blockchain) + for (const Block& blk : blockchain) { - BOOST_FOREACH(const crypto::hash& tx_hash, blk.tx_hashes) + for (const crypto::hash& tx_hash : blk.txHashes) { confirmed_hashes.insert(tx_hash); } @@ -604,18 +358,18 @@ void get_confirmed_txs(const std::vector& blockchain, const m } } -bool find_block_chain(const std::vector& events, std::vector& blockchain, map_hash2tx_t& mtx, const crypto::hash& head) { - std::unordered_map block_index; +bool find_block_chain(const std::vector& events, std::vector& blockchain, map_hash2tx_t& mtx, const crypto::hash& head) { + std::unordered_map block_index; BOOST_FOREACH(const test_event_entry& ev, events) { - if (typeid(block) == ev.type()) + if (typeid(Block) == ev.type()) { - const block* blk = &boost::get(ev); + const Block* blk = &boost::get(ev); block_index[get_block_hash(*blk)] = blk; } - else if (typeid(transaction) == ev.type()) + else if (typeid(Transaction) == ev.type()) { - const transaction& tx = boost::get(ev); + const Transaction& tx = boost::get(ev); mtx[get_transaction_hash(tx)] = &tx; } } @@ -625,7 +379,7 @@ bool find_block_chain(const std::vector& events, std::vectorsecond); - id = it->second->prev_id; + id = it->second->prevId; if (null_hash == id) { b_success = true; @@ -638,10 +392,16 @@ bool find_block_chain(const std::vector& events, std::vector &events) { auto cb_it = m_callbacks.find(cb_name); diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index fca74ff5..4fedf5ee 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -17,28 +17,16 @@ #pragma once -#include -#include -#include - -#include -#include #include -#include #include -#include "include_base_utils.h" #include "common/boost_serialization_helper.h" #include "common/command_line.h" - #include "cryptonote_core/account_boost_serialization.h" -#include "cryptonote_core/cryptonote_basic.h" -#include "cryptonote_core/cryptonote_basic_impl.h" -#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_core/account.h" #include "cryptonote_core/cryptonote_core.h" -#include "cryptonote_core/cryptonote_boost_serialization.h" -#include "misc_language.h" +#include "../TestGenerator/TestGenerator.h" namespace concolor { @@ -122,8 +110,8 @@ private: } }; -typedef serialized_object serialized_block; -typedef serialized_object serialized_transaction; +typedef serialized_object serialized_block; +typedef serialized_object serialized_transaction; struct event_visitor_settings { @@ -158,115 +146,57 @@ VARIANT_TAG(binary_archive, serialized_block, 0xcd); VARIANT_TAG(binary_archive, serialized_transaction, 0xce); VARIANT_TAG(binary_archive, event_visitor_settings, 0xcf); -typedef boost::variant test_event_entry; -typedef std::unordered_map map_hash2tx_t; +typedef boost::variant test_event_entry; +typedef std::unordered_map map_hash2tx_t; -class test_chain_unit_base +class test_chain_unit_base: boost::noncopyable { public: - typedef boost::function &events)> verify_callback; + test_chain_unit_base() : + m_currency(cryptonote::CurrencyBuilder().currency()) { + } + + typedef std::function &events)> verify_callback; typedef std::map callbacks_map; + const cryptonote::Currency& currency() const; void register_callback(const std::string& cb_name, verify_callback cb); bool verify(const std::string& cb_name, cryptonote::core& c, size_t ev_index, const std::vector &events); + +protected: + cryptonote::Currency m_currency; + private: callbacks_map m_callbacks; }; -class test_generator -{ -public: - struct block_info - { - block_info() - : prev_id() - , already_generated_coins(0) - , block_size(0) - { - } - - block_info(crypto::hash a_prev_id, uint64_t an_already_generated_coins, size_t a_block_size) - : prev_id(a_prev_id) - , already_generated_coins(an_already_generated_coins) - , block_size(a_block_size) - { - } - - crypto::hash prev_id; - uint64_t already_generated_coins; - size_t block_size; - }; - - enum block_fields - { - bf_none = 0, - bf_major_ver = 1 << 0, - bf_minor_ver = 1 << 1, - bf_timestamp = 1 << 2, - bf_prev_id = 1 << 3, - bf_miner_tx = 1 << 4, - bf_tx_hashes = 1 << 5, - bf_diffic = 1 << 6 - }; - - void get_block_chain(std::vector& blockchain, const crypto::hash& head, size_t n) const; - void get_last_n_block_sizes(std::vector& block_sizes, const crypto::hash& head, size_t n) const; - uint64_t get_already_generated_coins(const crypto::hash& blk_id) const; - uint64_t get_already_generated_coins(const cryptonote::block& blk) const; - - void add_block(const cryptonote::block& blk, size_t tsx_size, std::vector& block_sizes, uint64_t already_generated_coins); - bool construct_block(cryptonote::block& blk, uint64_t height, const crypto::hash& prev_id, - const cryptonote::account_base& miner_acc, uint64_t timestamp, uint64_t already_generated_coins, - std::vector& block_sizes, const std::list& tx_list); - bool construct_block(cryptonote::block& blk, const cryptonote::account_base& miner_acc, uint64_t timestamp); - bool construct_block(cryptonote::block& blk, const cryptonote::block& blk_prev, const cryptonote::account_base& miner_acc, - const std::list& tx_list = std::list()); - - bool construct_block_manually(cryptonote::block& blk, const cryptonote::block& prev_block, - const cryptonote::account_base& miner_acc, int actual_params = bf_none, uint8_t major_ver = 0, - uint8_t minor_ver = 0, uint64_t timestamp = 0, const crypto::hash& prev_id = crypto::hash(), - const cryptonote::difficulty_type& diffic = 1, const cryptonote::transaction& miner_tx = cryptonote::transaction(), - const std::vector& tx_hashes = std::vector(), size_t txs_sizes = 0); - bool construct_block_manually_tx(cryptonote::block& blk, const cryptonote::block& prev_block, - const cryptonote::account_base& miner_acc, const std::vector& tx_hashes, size_t txs_size); - -private: - std::unordered_map m_blocks_info; -}; - -inline cryptonote::difficulty_type get_test_difficulty() {return 1;} -void fill_nonce(cryptonote::block& blk, const cryptonote::difficulty_type& diffic, uint64_t height); - -bool construct_miner_tx_manually(size_t height, uint64_t already_generated_coins, - const cryptonote::account_public_address& miner_address, cryptonote::transaction& tx, - uint64_t fee, cryptonote::keypair* p_txkey = 0); -bool construct_tx_to_key(const std::vector& events, cryptonote::transaction& tx, - const cryptonote::block& blk_head, const cryptonote::account_base& from, const cryptonote::account_base& to, +bool construct_tx_to_key(const std::vector& events, cryptonote::Transaction& tx, + const cryptonote::Block& blk_head, const cryptonote::account_base& from, const cryptonote::account_base& to, uint64_t amount, uint64_t fee, size_t nmix); -cryptonote::transaction construct_tx_with_fee(std::vector& events, const cryptonote::block& blk_head, +cryptonote::Transaction construct_tx_with_fee(std::vector& events, const cryptonote::Block& blk_head, const cryptonote::account_base& acc_from, const cryptonote::account_base& acc_to, uint64_t amount, uint64_t fee); -void get_confirmed_txs(const std::vector& blockchain, const map_hash2tx_t& mtx, map_hash2tx_t& confirmed_txs); -bool find_block_chain(const std::vector& events, std::vector& blockchain, map_hash2tx_t& mtx, const crypto::hash& head); -void fill_tx_sources_and_destinations(const std::vector& events, const cryptonote::block& blk_head, +void get_confirmed_txs(const std::vector& blockchain, const map_hash2tx_t& mtx, map_hash2tx_t& confirmed_txs); +bool find_block_chain(const std::vector& events, std::vector& blockchain, map_hash2tx_t& mtx, const crypto::hash& head); +void fill_tx_sources_and_destinations(const std::vector& events, const cryptonote::Block& blk_head, const cryptonote::account_base& from, const cryptonote::account_base& to, uint64_t amount, uint64_t fee, size_t nmix, std::vector& sources, std::vector& destinations); -uint64_t get_balance(const cryptonote::account_base& addr, const std::vector& blockchain, const map_hash2tx_t& mtx); +uint64_t get_balance(const cryptonote::account_base& addr, const std::vector& blockchain, const map_hash2tx_t& mtx); //-------------------------------------------------------------------------- template -auto do_check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_index, const cryptonote::transaction& tx, t_test_class& validator, int) +auto do_check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_index, const cryptonote::Transaction& tx, t_test_class& validator, int) -> decltype(validator.check_tx_verification_context(tvc, tx_added, event_index, tx)) { return validator.check_tx_verification_context(tvc, tx_added, event_index, tx); } //-------------------------------------------------------------------------- template -bool do_check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t /*event_index*/, const cryptonote::transaction& /*tx*/, t_test_class&, long) +bool do_check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t /*event_index*/, const cryptonote::Transaction& /*tx*/, t_test_class&, long) { // Default block verification context check if (tvc.m_verifivation_failed) @@ -275,21 +205,21 @@ bool do_check_tx_verification_context(const cryptonote::tx_verification_context& } //-------------------------------------------------------------------------- template -bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_index, const cryptonote::transaction& tx, t_test_class& validator) +bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_index, const cryptonote::Transaction& tx, t_test_class& validator) { // SFINAE in action return do_check_tx_verification_context(tvc, tx_added, event_index, tx, validator, 0); } //-------------------------------------------------------------------------- template -auto do_check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_index, const cryptonote::block& blk, t_test_class& validator, int) +auto do_check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_index, const cryptonote::Block& blk, t_test_class& validator, int) -> decltype(validator.check_block_verification_context(bvc, event_index, blk)) { return validator.check_block_verification_context(bvc, event_index, blk); } //-------------------------------------------------------------------------- template -bool do_check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t /*event_index*/, const cryptonote::block& /*blk*/, t_test_class&, long) +bool do_check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t /*event_index*/, const cryptonote::Block& /*blk*/, t_test_class&, long) { // Default block verification context check if (bvc.m_verifivation_failed) @@ -298,7 +228,7 @@ bool do_check_block_verification_context(const cryptonote::block_verification_co } //-------------------------------------------------------------------------- template -bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_index, const cryptonote::block& blk, t_test_class& validator) +bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_index, const cryptonote::Block& blk, t_test_class& validator) { // SFINAE in action return do_check_block_verification_context(bvc, event_index, blk, validator, 0); @@ -345,11 +275,11 @@ public: return true; } - bool operator()(const cryptonote::transaction& tx) const + bool operator()(const cryptonote::Transaction& tx) const { - log_event("cryptonote::transaction"); + log_event("cryptonote::Transaction"); - cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); + cryptonote::tx_verification_context tvc = boost::value_initialized(); size_t pool_size = m_c.get_pool_transactions_count(); m_c.handle_incoming_tx(t_serializable_object_to_blob(tx), tvc, m_txs_keeped_by_block); bool tx_added = pool_size + 1 == m_c.get_pool_transactions_count(); @@ -358,12 +288,12 @@ public: return true; } - bool operator()(const cryptonote::block& b) const + bool operator()(const cryptonote::Block& b) const { - log_event("cryptonote::block"); + log_event("cryptonote::Block"); - cryptonote::block_verification_context bvc = AUTO_VAL_INIT(bvc); - m_c.handle_incoming_block(t_serializable_object_to_blob(b), bvc); + cryptonote::block_verification_context bvc = boost::value_initialized(); + m_c.handle_incoming_block_blob(t_serializable_object_to_blob(b), bvc, false, false); bool r = check_block_verification_context(bvc, m_ev_index, b, m_validator); CHECK_AND_NO_ASSERT_MES(r, false, "block verification context check failed"); return r; @@ -385,17 +315,17 @@ public: { log_event("serialized_block"); - cryptonote::block_verification_context bvc = AUTO_VAL_INIT(bvc); - m_c.handle_incoming_block(sr_block.data, bvc); + cryptonote::block_verification_context bvc = boost::value_initialized(); + m_c.handle_incoming_block_blob(sr_block.data, bvc, false, false); - cryptonote::block blk; + cryptonote::Block blk; std::stringstream ss; ss << sr_block.data; binary_archive ba(ss); ::serialization::serialize(ba, blk); if (!ss.good()) { - blk = cryptonote::block(); + blk = cryptonote::Block(); } bool r = check_block_verification_context(bvc, m_ev_index, blk, m_validator); CHECK_AND_NO_ASSERT_MES(r, false, "block verification context check failed"); @@ -406,19 +336,19 @@ public: { log_event("serialized_transaction"); - cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); + cryptonote::tx_verification_context tvc = boost::value_initialized();; size_t pool_size = m_c.get_pool_transactions_count(); m_c.handle_incoming_tx(sr_tx.data, tvc, m_txs_keeped_by_block); bool tx_added = pool_size + 1 == m_c.get_pool_transactions_count(); - cryptonote::transaction tx; + cryptonote::Transaction tx; std::stringstream ss; ss << sr_tx.data; binary_archive ba(ss); ::serialization::serialize(ba, tx); if (!ss.good()) { - tx = cryptonote::transaction(); + tx = cryptonote::Transaction(); } bool r = check_tx_verification_context(tvc, tx_added, m_ev_index, tx, m_validator); @@ -440,8 +370,8 @@ inline bool replay_events_through_core(cryptonote::core& cr, const std::vector(events[0])); + CHECK_AND_ASSERT_MES(typeid(cryptonote::Block) == events[0].type(), false, "First event must be genesis block creation"); + cr.set_genesis_block(boost::get(events[0])); bool r = true; push_core_event_visitor visitor(cr, events, validator); @@ -457,7 +387,7 @@ inline bool replay_events_through_core(cryptonote::core& cr, const std::vector -inline bool do_replay_events(std::vector& events) +inline bool do_replay_events(std::vector& events, t_test_class& validator) { boost::program_options::options_description desc("Allowed options"); cryptonote::core::init_options(desc); @@ -473,13 +403,13 @@ inline bool do_replay_events(std::vector& events) return false; cryptonote::cryptonote_protocol_stub pr; //TODO: stub only for this kind of test, make real validation of relayed objects - cryptonote::core c(&pr); + cryptonote::core c(validator.currency(), &pr); if (!c.init(vm, false)) { std::cout << concolor::magenta << "Failed to init core" << concolor::normal << std::endl; return false; } - t_test_class validator; + return replay_events_through_core(c, events, validator); } //-------------------------------------------------------------------------- @@ -492,7 +422,8 @@ inline bool do_replay_file(const std::string& filename) std::cout << concolor::magenta << "Failed to deserialize data from file: " << filename << concolor::normal << std::endl; return false; } - return do_replay_events(events); + t_test_class validator; + return do_replay_events(events, validator); } //-------------------------------------------------------------------------- #define GENERATE_ACCOUNT(account) \ @@ -512,40 +443,40 @@ inline bool do_replay_file(const std::string& filename) } #define REGISTER_CALLBACK(CB_NAME, CLBACK) \ - register_callback(CB_NAME, boost::bind(&CLBACK, this, _1, _2, _3)); + register_callback(CB_NAME, std::bind(&CLBACK, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); #define REGISTER_CALLBACK_METHOD(CLASS, METHOD) \ - register_callback(#METHOD, boost::bind(&CLASS::METHOD, this, _1, _2, _3)); + register_callback(#METHOD, std::bind(&CLASS::METHOD, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); #define MAKE_GENESIS_BLOCK(VEC_EVENTS, BLK_NAME, MINER_ACC, TS) \ - test_generator generator; \ - cryptonote::block BLK_NAME; \ - generator.construct_block(BLK_NAME, MINER_ACC, TS); \ + test_generator generator(this->m_currency); \ + cryptonote::Block BLK_NAME; \ + generator.constructBlock(BLK_NAME, MINER_ACC, TS); \ VEC_EVENTS.push_back(BLK_NAME); #define MAKE_NEXT_BLOCK(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC) \ - cryptonote::block BLK_NAME; \ - generator.construct_block(BLK_NAME, PREV_BLOCK, MINER_ACC); \ + cryptonote::Block BLK_NAME; \ + generator.constructBlock(BLK_NAME, PREV_BLOCK, MINER_ACC); \ VEC_EVENTS.push_back(BLK_NAME); #define MAKE_NEXT_BLOCK_TX1(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, TX1) \ - cryptonote::block BLK_NAME; \ + cryptonote::Block BLK_NAME; \ { \ - std::list tx_list; \ + std::list tx_list; \ tx_list.push_back(TX1); \ - generator.construct_block(BLK_NAME, PREV_BLOCK, MINER_ACC, tx_list); \ + generator.constructBlock(BLK_NAME, PREV_BLOCK, MINER_ACC, tx_list); \ } \ VEC_EVENTS.push_back(BLK_NAME); #define MAKE_NEXT_BLOCK_TX_LIST(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, TXLIST) \ - cryptonote::block BLK_NAME; \ - generator.construct_block(BLK_NAME, PREV_BLOCK, MINER_ACC, TXLIST); \ + cryptonote::Block BLK_NAME; \ + generator.constructBlock(BLK_NAME, PREV_BLOCK, MINER_ACC, TXLIST); \ VEC_EVENTS.push_back(BLK_NAME); #define REWIND_BLOCKS_N(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, COUNT) \ - cryptonote::block BLK_NAME; \ + cryptonote::Block BLK_NAME; \ { \ - cryptonote::block blk_last = PREV_BLOCK; \ + cryptonote::Block blk_last = PREV_BLOCK; \ for (size_t i = 0; i < COUNT; ++i) \ { \ MAKE_NEXT_BLOCK(VEC_EVENTS, blk, blk_last, MINER_ACC); \ @@ -554,33 +485,34 @@ inline bool do_replay_file(const std::string& filename) BLK_NAME = blk_last; \ } -#define REWIND_BLOCKS(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC) REWIND_BLOCKS_N(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW) +#define REWIND_BLOCKS(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC) \ + REWIND_BLOCKS_N(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, this->m_currency.minedMoneyUnlockWindow()) -#define MAKE_TX_MIX(VEC_EVENTS, TX_NAME, FROM, TO, AMOUNT, NMIX, HEAD) \ - cryptonote::transaction TX_NAME; \ - construct_tx_to_key(VEC_EVENTS, TX_NAME, HEAD, FROM, TO, AMOUNT, TESTS_DEFAULT_FEE, NMIX); \ +#define MAKE_TX_MIX(VEC_EVENTS, TX_NAME, FROM, TO, AMOUNT, NMIX, HEAD) \ + cryptonote::Transaction TX_NAME; \ + construct_tx_to_key(VEC_EVENTS, TX_NAME, HEAD, FROM, TO, AMOUNT, this->m_currency.minimumFee(), NMIX); \ VEC_EVENTS.push_back(TX_NAME); #define MAKE_TX(VEC_EVENTS, TX_NAME, FROM, TO, AMOUNT, HEAD) MAKE_TX_MIX(VEC_EVENTS, TX_NAME, FROM, TO, AMOUNT, 0, HEAD) -#define MAKE_TX_MIX_LIST(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, NMIX, HEAD) \ - { \ - cryptonote::transaction t; \ - construct_tx_to_key(VEC_EVENTS, t, HEAD, FROM, TO, AMOUNT, TESTS_DEFAULT_FEE, NMIX); \ - SET_NAME.push_back(t); \ - VEC_EVENTS.push_back(t); \ +#define MAKE_TX_MIX_LIST(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, NMIX, HEAD) \ + { \ + cryptonote::Transaction t; \ + construct_tx_to_key(VEC_EVENTS, t, HEAD, FROM, TO, AMOUNT, this->m_currency.minimumFee(), NMIX); \ + SET_NAME.push_back(t); \ + VEC_EVENTS.push_back(t); \ } #define MAKE_TX_LIST(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, HEAD) MAKE_TX_MIX_LIST(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, 0, HEAD) #define MAKE_TX_LIST_START(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, HEAD) \ - std::list SET_NAME; \ + std::list SET_NAME; \ MAKE_TX_LIST(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, HEAD); -#define MAKE_MINER_TX_AND_KEY_MANUALLY(TX, BLK, KEY) \ - transaction TX; \ - if (!construct_miner_tx_manually(get_block_height(BLK) + 1, generator.get_already_generated_coins(BLK), \ - miner_account.get_keys().m_account_address, TX, 0, KEY)) \ +#define MAKE_MINER_TX_AND_KEY_MANUALLY(TX, BLK, KEY) \ + Transaction TX; \ + if (!constructMinerTxManually(this->m_currency, get_block_height(BLK) + 1, generator.getAlreadyGeneratedCoins(BLK), \ + miner_account.get_keys().m_account_address, TX, 0, KEY)) \ return false; #define MAKE_MINER_TX_MANUALLY(TX, BLK) MAKE_MINER_TX_AND_KEY_MANUALLY(TX, BLK, 0) @@ -625,7 +557,8 @@ inline bool do_replay_file(const std::string& filename) { \ LOG_PRINT(#genclass << " generation failed: generic exception", 0); \ } \ - if (generated && do_replay_events< genclass >(events)) \ + genclass validator; \ + if (generated && do_replay_events< genclass >(events, validator)) \ { \ std::cout << concolor::green << "#TEST# Succeeded " << #genclass << concolor::normal << '\n'; \ } \ @@ -637,6 +570,35 @@ inline bool do_replay_file(const std::string& filename) std::cout << std::endl; \ } + +template +bool GenerateAndPlay(const char* testname, GenClassT&& g) { + std::vector events; + bool generated = false; + + try { + generated = g.generate(events); + } catch (const std::exception& ex) { + LOG_PRINT(testname << " generation failed: what=" << ex.what(), 0); + } catch (...) { + LOG_PRINT(testname << " generation failed: generic exception", 0); + } + + bool succeeded = generated && do_replay_events(events, g); + + if (succeeded) { + std::cout << concolor::green << "#TEST# Succeeded " << testname << concolor::normal << '\n'; + } else { + std::cout << concolor::magenta << "#TEST# Failed " << testname << concolor::normal << '\n'; + } + + std::cout << std::endl; + return succeeded; +} + +#define GENERATE_AND_PLAY_EX(genclass) { ++tests_count; if (!GenerateAndPlay(#genclass, genclass)) failed_tests.push_back(#genclass); } + + #define CALL_TEST(test_name, function) \ { \ if(!function()) \ @@ -655,5 +617,4 @@ inline bool do_replay_file(const std::string& filename) #define CHECK_TEST_CONDITION(cond) CHECK_AND_ASSERT_MES(cond, false, "[" << perr_context << "] failed: \"" << QUOTEME(cond) << "\"") #define CHECK_EQ(v1, v2) CHECK_AND_ASSERT_MES(v1 == v2, false, "[" << perr_context << "] failed: \"" << QUOTEME(v1) << " == " << QUOTEME(v2) << "\", " << v1 << " != " << v2) #define CHECK_NOT_EQ(v1, v2) CHECK_AND_ASSERT_MES(!(v1 == v2), false, "[" << perr_context << "] failed: \"" << QUOTEME(v1) << " != " << QUOTEME(v2) << "\", " << v1 << " == " << v2) -#define MK_COINS(amount) (UINT64_C(amount) * COIN) -#define TESTS_DEFAULT_FEE (MINIMUM_FEE) +#define MK_COINS(amount) (UINT64_C(amount) * cryptonote::parameters::COIN) diff --git a/tests/core_tests/chaingen001.cpp b/tests/core_tests/chaingen001.cpp index 0d59f887..d32a01ac 100644 --- a/tests/core_tests/chaingen001.cpp +++ b/tests/core_tests/chaingen001.cpp @@ -15,18 +15,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . -#include -#include - -#include "include_base_utils.h" - -#include "console_handler.h" - -#include "cryptonote_core/cryptonote_basic.h" -#include "cryptonote_core/cryptonote_format_utils.h" - -#include "chaingen.h" -#include "chaingen_tests_list.h" +#include "chaingen001.h" using namespace std; @@ -59,13 +48,13 @@ bool one_block::verify_1(cryptonote::core& c, size_t ev_index, const std::vector alice = boost::get(events[1]); // check balances - //std::vector chain; + //std::vector chain; //map_hash2tx_t mtx; - //CHECK_TEST_CONDITION(find_block_chain(events, chain, mtx, get_block_hash(boost::get(events[1])))); + //CHECK_TEST_CONDITION(find_block_chain(events, chain, mtx, get_block_hash(boost::get(events[1])))); //CHECK_TEST_CONDITION(get_block_reward(0) == get_balance(alice, events, chain, mtx)); // check height - std::list blocks; + std::list blocks; std::list outs; bool r = c.get_blocks(0, 100, blocks); //c.get_outs(100, outs); @@ -73,7 +62,7 @@ bool one_block::verify_1(cryptonote::core& c, size_t ev_index, const std::vector CHECK_TEST_CONDITION(blocks.size() == 1); //CHECK_TEST_CONDITION(outs.size() == blocks.size()); CHECK_TEST_CONDITION(c.get_blockchain_total_transactions() == 1); - CHECK_TEST_CONDITION(blocks.back() == boost::get(events[0])); + CHECK_TEST_CONDITION(blocks.back() == boost::get(events[0])); return true; } @@ -101,9 +90,9 @@ bool gen_simple_chain_001::generate(std::vector &events) MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner); //MAKE_TX(events, tx_0, first_miner_account, alice, 151, blk_2); - std::vector chain; + std::vector chain; map_hash2tx_t mtx; - /*bool r = */find_block_chain(events, chain, mtx, get_block_hash(boost::get(events[3]))); + /*bool r = */find_block_chain(events, chain, mtx, get_block_hash(boost::get(events[3]))); std::cout << "BALANCE = " << get_balance(miner, chain, mtx) << std::endl; REWIND_BLOCKS(events, blk_2r, blk_2, miner); @@ -127,7 +116,7 @@ bool gen_simple_chain_001::generate(std::vector &events) //MAKE_BLOCK_TX1(events, blk_3, 3, get_block_hash(blk_0), get_test_target(), first_miner_account, ts_start + 10, tx_0); //DO_CALLBACK(events, "verify_callback_2"); -/* std::vector chain; +/* std::vector chain; map_hash2tx_t mtx; if (!find_block_chain(events, chain, mtx, get_block_hash(blk_6))) throw; diff --git a/tests/core_tests/chaingen_tests_list.h b/tests/core_tests/chaingen001.h similarity index 75% rename from tests/core_tests/chaingen_tests_list.h rename to tests/core_tests/chaingen001.h index f08d7417..63de1d74 100644 --- a/tests/core_tests/chaingen_tests_list.h +++ b/tests/core_tests/chaingen001.h @@ -15,20 +15,9 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . -#pragma once - +#pragma once #include "chaingen.h" -#include "block_reward.h" -#include "block_validation.h" -#include "chain_split_1.h" -#include "chain_switch_1.h" -#include "double_spend.h" -#include "integer_overflow.h" -#include "ring_signature_1.h" -#include "tx_validation.h" -/************************************************************************/ -/* */ -/************************************************************************/ + class gen_simple_chain_001: public test_chain_unit_base { public: diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 94b14823..134f4ece 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -16,9 +16,20 @@ // along with Bytecoin. If not, see . #include "chaingen.h" -#include "chaingen_tests_list.h" + #include "common/command_line.h" + +#include "block_reward.h" +#include "block_validation.h" +#include "chain_split_1.h" +#include "chain_switch_1.h" +#include "chaingen001.h" +#include "double_spend.h" +#include "integer_overflow.h" +#include "ring_signature_1.h" #include "transaction_tests.h" +#include "tx_validation.h" +#include "upgrade.h" namespace po = boost::program_options; @@ -81,6 +92,10 @@ int main(int argc, char* argv[]) } else if (command_line::get_arg(vm, arg_generate_and_play_test_data)) { +#define GENERATE_AND_PLAY_EX_2VER(TestCase) \ + GENERATE_AND_PLAY_EX(TestCase(cryptonote::BLOCK_MAJOR_VERSION_1)) \ + GENERATE_AND_PLAY_EX(TestCase(cryptonote::BLOCK_MAJOR_VERSION_2)) + GENERATE_AND_PLAY(gen_simple_chain_001); GENERATE_AND_PLAY(gen_simple_chain_split_1); GENERATE_AND_PLAY(one_block); @@ -90,40 +105,50 @@ int main(int argc, char* argv[]) //GENERATE_AND_PLAY(gen_ring_signature_big); // Takes up to XXX hours (if CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW == 10) // Block verification tests - GENERATE_AND_PLAY(gen_block_big_major_version); - GENERATE_AND_PLAY(gen_block_big_minor_version); - GENERATE_AND_PLAY(gen_block_ts_not_checked); - GENERATE_AND_PLAY(gen_block_ts_in_past); - GENERATE_AND_PLAY(gen_block_ts_in_future); - GENERATE_AND_PLAY(gen_block_invalid_prev_id); - GENERATE_AND_PLAY(gen_block_invalid_nonce); - GENERATE_AND_PLAY(gen_block_no_miner_tx); - GENERATE_AND_PLAY(gen_block_unlock_time_is_low); - GENERATE_AND_PLAY(gen_block_unlock_time_is_high); - GENERATE_AND_PLAY(gen_block_unlock_time_is_timestamp_in_past); - GENERATE_AND_PLAY(gen_block_unlock_time_is_timestamp_in_future); - GENERATE_AND_PLAY(gen_block_height_is_low); - GENERATE_AND_PLAY(gen_block_height_is_high); - GENERATE_AND_PLAY(gen_block_miner_tx_has_2_tx_gen_in); - GENERATE_AND_PLAY(gen_block_miner_tx_has_2_in); - GENERATE_AND_PLAY(gen_block_miner_tx_with_txin_to_key); - GENERATE_AND_PLAY(gen_block_miner_tx_out_is_small); - GENERATE_AND_PLAY(gen_block_miner_tx_out_is_big); - GENERATE_AND_PLAY(gen_block_miner_tx_has_no_out); - GENERATE_AND_PLAY(gen_block_miner_tx_has_out_to_alice); - GENERATE_AND_PLAY(gen_block_has_invalid_tx); - GENERATE_AND_PLAY(gen_block_is_too_big); - GENERATE_AND_PLAY(gen_block_invalid_binary_format); // Takes up to 3 hours, if CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW == 500, up to 30 minutes, if CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW == 10 + GENERATE_AND_PLAY_EX_2VER(TestBlockMajorVersionAccepted); + GENERATE_AND_PLAY_EX(TestBlockMajorVersionRejected(cryptonote::BLOCK_MAJOR_VERSION_1, cryptonote::BLOCK_MAJOR_VERSION_2)); + GENERATE_AND_PLAY_EX(TestBlockMajorVersionRejected(cryptonote::BLOCK_MAJOR_VERSION_2, cryptonote::BLOCK_MAJOR_VERSION_1)); + GENERATE_AND_PLAY_EX(TestBlockMajorVersionRejected(cryptonote::BLOCK_MAJOR_VERSION_2, cryptonote::BLOCK_MAJOR_VERSION_2 + 1)); + GENERATE_AND_PLAY_EX_2VER(TestBlockBigMinorVersion); + GENERATE_AND_PLAY_EX_2VER(gen_block_ts_not_checked); + GENERATE_AND_PLAY_EX_2VER(gen_block_ts_in_past); + GENERATE_AND_PLAY_EX_2VER(gen_block_ts_in_future_rejected); + GENERATE_AND_PLAY_EX_2VER(gen_block_ts_in_future_accepted); + GENERATE_AND_PLAY_EX_2VER(gen_block_invalid_prev_id); + GENERATE_AND_PLAY_EX_2VER(gen_block_invalid_nonce); + GENERATE_AND_PLAY_EX_2VER(gen_block_no_miner_tx); + GENERATE_AND_PLAY_EX_2VER(gen_block_unlock_time_is_low); + GENERATE_AND_PLAY_EX_2VER(gen_block_unlock_time_is_high); + GENERATE_AND_PLAY_EX_2VER(gen_block_unlock_time_is_timestamp_in_past); + GENERATE_AND_PLAY_EX_2VER(gen_block_unlock_time_is_timestamp_in_future); + GENERATE_AND_PLAY_EX_2VER(gen_block_height_is_low); + GENERATE_AND_PLAY_EX_2VER(gen_block_height_is_high); + GENERATE_AND_PLAY_EX_2VER(gen_block_miner_tx_has_2_tx_gen_in); + GENERATE_AND_PLAY_EX_2VER(gen_block_miner_tx_has_2_in); + GENERATE_AND_PLAY_EX_2VER(gen_block_miner_tx_with_txin_to_key); + GENERATE_AND_PLAY_EX_2VER(gen_block_miner_tx_out_is_small); + GENERATE_AND_PLAY_EX_2VER(gen_block_miner_tx_out_is_big); + GENERATE_AND_PLAY_EX_2VER(gen_block_miner_tx_has_no_out); + GENERATE_AND_PLAY_EX_2VER(gen_block_miner_tx_has_out_to_alice); + GENERATE_AND_PLAY_EX_2VER(gen_block_has_invalid_tx); + GENERATE_AND_PLAY_EX_2VER(gen_block_is_too_big); + GENERATE_AND_PLAY_EX_2VER(TestBlockCumulativeSizeExceedsLimit); + GENERATE_AND_PLAY_EX_2VER(gen_block_invalid_binary_format); // Takes up to 30 minutes, if CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW == 10 + + GENERATE_AND_PLAY(TestMaxSizeOfParentBlock); + GENERATE_AND_PLAY(TestBigParentBlock); + GENERATE_AND_PLAY(TestBlock2ExtraEmpty); + GENERATE_AND_PLAY(TestBlock2ExtraWithoutMMTag); + GENERATE_AND_PLAY(TestBlock2ExtraWithGarbage); // Transaction verification tests GENERATE_AND_PLAY(gen_tx_big_version); GENERATE_AND_PLAY(gen_tx_unlock_time); - GENERATE_AND_PLAY(gen_tx_input_is_not_txin_to_key); GENERATE_AND_PLAY(gen_tx_no_inputs_no_outputs); GENERATE_AND_PLAY(gen_tx_no_inputs_has_outputs); GENERATE_AND_PLAY(gen_tx_has_inputs_no_outputs); GENERATE_AND_PLAY(gen_tx_invalid_input_amount); - GENERATE_AND_PLAY(gen_tx_input_wo_key_offsets); + GENERATE_AND_PLAY(gen_tx_in_to_key_wo_key_offsets); GENERATE_AND_PLAY(gen_tx_sender_key_offest_not_exist); GENERATE_AND_PLAY(gen_tx_key_offest_points_to_foreign_key); GENERATE_AND_PLAY(gen_tx_mixed_key_offest_not_exist); @@ -132,9 +157,28 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(gen_tx_check_input_unlock_time); GENERATE_AND_PLAY(gen_tx_txout_to_key_has_invalid_key); GENERATE_AND_PLAY(gen_tx_output_with_zero_amount); - GENERATE_AND_PLAY(gen_tx_output_is_not_txout_to_key); GENERATE_AND_PLAY(gen_tx_signatures_are_invalid); + // multisignature output + GENERATE_AND_PLAY_EX(MultiSigTx_OutputSignatures(1, 1, true)); + GENERATE_AND_PLAY_EX(MultiSigTx_OutputSignatures(2, 2, true)); + GENERATE_AND_PLAY_EX(MultiSigTx_OutputSignatures(3, 2, true)); + GENERATE_AND_PLAY_EX(MultiSigTx_OutputSignatures(0, 0, false)); + GENERATE_AND_PLAY_EX(MultiSigTx_OutputSignatures(1, 0, false)); + GENERATE_AND_PLAY_EX(MultiSigTx_OutputSignatures(0, 1, false)); + GENERATE_AND_PLAY_EX(MultiSigTx_OutputSignatures(1, 2, false)); + GENERATE_AND_PLAY_EX(MultiSigTx_OutputSignatures(2, 3, false)); + GENERATE_AND_PLAY_EX(MultiSigTx_InvalidOutputSignature()); + + // multisignature input + GENERATE_AND_PLAY_EX(MultiSigTx_Input(1, 1, 1, true)); + GENERATE_AND_PLAY_EX(MultiSigTx_Input(2, 1, 1, true)); + GENERATE_AND_PLAY_EX(MultiSigTx_Input(3, 2, 2, true)); + GENERATE_AND_PLAY_EX(MultiSigTx_Input(1, 1, 0, false)); + GENERATE_AND_PLAY_EX(MultiSigTx_Input(2, 2, 1, false)); + GENERATE_AND_PLAY_EX(MultiSigTx_Input(3, 2, 1, false)); + GENERATE_AND_PLAY_EX(MultiSigTx_BadInputSignature()); + // Double spend GENERATE_AND_PLAY(gen_double_spend_in_tx); GENERATE_AND_PLAY(gen_double_spend_in_tx); @@ -148,10 +192,23 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(gen_double_spend_in_alt_chain_in_different_blocks); GENERATE_AND_PLAY(gen_double_spend_in_alt_chain_in_different_blocks); + GENERATE_AND_PLAY_EX(MultiSigTx_DoubleSpendInTx(false)); + GENERATE_AND_PLAY_EX(MultiSigTx_DoubleSpendInTx(true)); + GENERATE_AND_PLAY_EX(MultiSigTx_DoubleSpendSameBlock(false)); + GENERATE_AND_PLAY_EX(MultiSigTx_DoubleSpendSameBlock(true)); + GENERATE_AND_PLAY_EX(MultiSigTx_DoubleSpendDifferentBlocks(false)); + GENERATE_AND_PLAY_EX(MultiSigTx_DoubleSpendDifferentBlocks(true)); + GENERATE_AND_PLAY_EX(MultiSigTx_DoubleSpendAltChainSameBlock(false)); + GENERATE_AND_PLAY_EX(MultiSigTx_DoubleSpendAltChainSameBlock(true)); + GENERATE_AND_PLAY_EX(MultiSigTx_DoubleSpendAltChainDifferentBlocks(false)); + GENERATE_AND_PLAY_EX(MultiSigTx_DoubleSpendAltChainDifferentBlocks(true)); + + GENERATE_AND_PLAY(gen_uint_overflow_1); GENERATE_AND_PLAY(gen_uint_overflow_2); GENERATE_AND_PLAY(gen_block_reward); + GENERATE_AND_PLAY(gen_upgrade); std::cout << (failed_tests.empty() ? concolor::green : concolor::magenta); std::cout << "\nREPORT:\n"; diff --git a/tests/core_tests/double_spend.cpp b/tests/core_tests/double_spend.cpp index 423f3ab5..1e5004ac 100644 --- a/tests/core_tests/double_spend.cpp +++ b/tests/core_tests/double_spend.cpp @@ -15,17 +15,18 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . -#include "chaingen.h" -#include "chaingen_tests_list.h" +#include "double_spend.h" +#include "TestGenerator.h" using namespace epee; using namespace cryptonote; - //====================================================================================================================== gen_double_spend_in_different_chains::gen_double_spend_in_different_chains() { + expected_blockchain_height = 4 + 2 * m_currency.minedMoneyUnlockWindow(); + REGISTER_CALLBACK_METHOD(gen_double_spend_in_different_chains, check_double_spend); } @@ -34,9 +35,9 @@ bool gen_double_spend_in_different_chains::generate(std::vector block_list; - bool r = c.get_blocks(0, 100 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, block_list); + std::list block_list; + bool r = c.get_blocks(0, 100 + 2 * m_currency.minedMoneyUnlockWindow(), block_list); CHECK_TEST_CONDITION(r); - std::vector blocks(block_list.begin(), block_list.end()); + std::vector blocks(block_list.begin(), block_list.end()); CHECK_EQ(expected_blockchain_height, blocks.size()); CHECK_EQ(1, c.get_pool_transactions_count()); @@ -72,12 +73,310 @@ bool gen_double_spend_in_different_chains::check_double_spend(cryptonote::core& cryptonote::account_base bob_account = boost::get(events[1]); cryptonote::account_base alice_account = boost::get(events[2]); - std::vector chain; + std::vector chain; map_hash2tx_t mtx; r = find_block_chain(events, chain, mtx, get_block_hash(blocks.back())); CHECK_TEST_CONDITION(r); CHECK_EQ(0, get_balance(bob_account, blocks, mtx)); - CHECK_EQ(send_amount - TESTS_DEFAULT_FEE, get_balance(alice_account, blocks, mtx)); + CHECK_EQ(send_amount - m_currency.minimumFee(), get_balance(alice_account, blocks, mtx)); + + return true; +} + +//====================================================================================================================== +// DoubleSpendBase +//====================================================================================================================== +DoubleSpendBase::DoubleSpendBase() : + m_invalid_tx_index(invalid_index_value), + m_invalid_block_index(invalid_index_value), + send_amount(MK_COINS(17)), + has_invalid_tx(false) +{ + m_bob_account.generate(); + m_alice_account.generate(); + + REGISTER_CALLBACK_METHOD(DoubleSpendBase, mark_last_valid_block); + REGISTER_CALLBACK_METHOD(DoubleSpendBase, mark_invalid_tx); + REGISTER_CALLBACK_METHOD(DoubleSpendBase, mark_invalid_block); + REGISTER_CALLBACK_METHOD(DoubleSpendBase, check_double_spend); +} + +bool DoubleSpendBase::check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::Transaction& /*tx*/) +{ + if (m_invalid_tx_index == event_idx) + return tvc.m_verifivation_failed; + else + return !tvc.m_verifivation_failed && tx_added; +} + +bool DoubleSpendBase::check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::Block& /*block*/) +{ + if (m_invalid_block_index == event_idx) + return bvc.m_verifivation_failed; + else + return !bvc.m_verifivation_failed; +} + +bool DoubleSpendBase::mark_last_valid_block(cryptonote::core& c, size_t /*ev_index*/, const std::vector& /*events*/) +{ + m_last_valid_block = c.get_blockchain_storage().get_tail_id(); + return true; +} + +bool DoubleSpendBase::mark_invalid_tx(cryptonote::core& /*c*/, size_t ev_index, const std::vector& /*events*/) +{ + m_invalid_tx_index = ev_index + 1; + return true; +} + +bool DoubleSpendBase::mark_invalid_block(cryptonote::core& /*c*/, size_t ev_index, const std::vector& /*events*/) +{ + m_invalid_block_index = ev_index + 1; + return true; +} + +bool DoubleSpendBase::check_double_spend(cryptonote::core& c, size_t /*ev_index*/, const std::vector& events) +{ + DEFINE_TESTS_ERROR_CONTEXT("DoubleSpendBase::check_double_spend"); + CHECK_EQ(m_last_valid_block, c.get_blockchain_storage().get_tail_id()); + return true; +} + +TestGenerator DoubleSpendBase::prepare(std::vector& events) const { + + TestGenerator generator(m_currency, events); + + // unlock + generator.generateBlocks(); + + auto builder = generator.createTxBuilder(generator.minerAccount, m_bob_account, send_amount, m_currency.minimumFee()); + + builder.m_destinations.clear(); + + TransactionBuilder::KeysVector kv; + kv.push_back(m_bob_account.get_keys()); + + builder.addMultisignatureOut(send_amount, kv, 1); + + // move money + auto tx = builder.build(); + + generator.addEvent(tx); + generator.makeNextBlock(tx); + + // unlock + generator.generateBlocks(); + + return generator; +} + +TransactionBuilder DoubleSpendBase::createBobToAliceTx() const { + TransactionBuilder builder(m_currency); + + TransactionInputMultisignature msigInput; + msigInput.amount = send_amount; + msigInput.outputIndex = 0; + msigInput.signatures = 1; + + TransactionBuilder::KeysVector kv; + kv.push_back(m_bob_account.get_keys()); + + builder. + addMultisignatureInput(msigInput, kv). + addOutput(tx_destination_entry(send_amount - m_currency.minimumFee(), m_alice_account.get_keys().m_account_address)); + + return builder; +} + +//====================================================================================================================== +// MultiSigTx_DoubleSpendInTx +//====================================================================================================================== + +MultiSigTx_DoubleSpendInTx::MultiSigTx_DoubleSpendInTx(bool txsKeepedByBlock) + : m_txsKeepedByBlock(txsKeepedByBlock) +{ + has_invalid_tx = true; +} + +bool MultiSigTx_DoubleSpendInTx::generate(std::vector& events) const { + TestGenerator generator(prepare(events)); + + generator.addCallback("mark_last_valid_block"); + + TransactionInputMultisignature msigInput; + msigInput.amount = send_amount; + msigInput.outputIndex = 0; + msigInput.signatures = 1; + + TransactionBuilder::KeysVector kv; + kv.push_back(m_bob_account.get_keys()); + + TransactionBuilder builder(generator.currency()); + + auto tx = builder. + addMultisignatureInput(msigInput, kv). + addMultisignatureInput(msigInput, kv). + addOutput(tx_destination_entry(send_amount*2 - m_currency.minimumFee(), m_alice_account.get_keys().m_account_address)). + build(); + + SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, m_txsKeepedByBlock); + + generator.addCallback("mark_invalid_tx"); // should be rejected by the core + generator.addEvent(tx); + generator.addCallback("mark_invalid_block"); + generator.makeNextBlock(tx); + generator.addCallback("check_double_spend"); + + return true; +} + +//====================================================================================================================== +// MultiSigTx_DoubleSpendSameBlock +//====================================================================================================================== +MultiSigTx_DoubleSpendSameBlock::MultiSigTx_DoubleSpendSameBlock(bool txsKeepedByBlock) + : m_txsKeepedByBlock(txsKeepedByBlock) { + has_invalid_tx = !txsKeepedByBlock; +} + +bool MultiSigTx_DoubleSpendSameBlock::generate(std::vector& events) const { + TestGenerator generator(prepare(events)); + + generator.addCallback("mark_last_valid_block"); + SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, m_txsKeepedByBlock); + + std::list txs; + + auto builder = createBobToAliceTx(); + + auto tx1 = builder.newTxKeys().build(); + auto tx2 = builder.newTxKeys().build(); + + generator.addEvent(tx1); + + if (has_invalid_tx) { + generator.addCallback("mark_invalid_tx"); + } + + generator.addEvent(tx2); + + txs.push_back(tx1); + txs.push_back(tx2); + + generator.addCallback("mark_invalid_block"); + generator.makeNextBlock(txs); + generator.addCallback("check_double_spend"); + + return true; +} + +//====================================================================================================================== +// MultiSigTx_DoubleSpendDifferentBlocks +//====================================================================================================================== +MultiSigTx_DoubleSpendDifferentBlocks::MultiSigTx_DoubleSpendDifferentBlocks(bool txsKeepedByBlock) + : m_txsKeepedByBlock(txsKeepedByBlock) { + has_invalid_tx = !txsKeepedByBlock; +} + +bool MultiSigTx_DoubleSpendDifferentBlocks::generate(std::vector& events) const { + TestGenerator generator(prepare(events)); + + generator.addCallback("mark_last_valid_block"); + SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, m_txsKeepedByBlock); + + auto builder = createBobToAliceTx(); + + auto tx1 = builder.build(); + + generator.addEvent(tx1); + generator.makeNextBlock(tx1); + generator.addCallback("mark_last_valid_block"); + + auto tx2 = builder.newTxKeys().build(); // same transaction, but different tx key + + if (has_invalid_tx) { + generator.addCallback("mark_invalid_tx"); + } + + generator.addEvent(tx2); + generator.addCallback("mark_invalid_block"); + generator.makeNextBlock(tx2); + generator.addCallback("check_double_spend"); + + return true; +} + +//====================================================================================================================== +// MultiSigTx_DoubleSpendAltChainSameBlock +//====================================================================================================================== + +MultiSigTx_DoubleSpendAltChainSameBlock::MultiSigTx_DoubleSpendAltChainSameBlock(bool txsKeepedByBlock) + : m_txsKeepedByBlock(txsKeepedByBlock) { + has_invalid_tx = !txsKeepedByBlock; +} + +bool MultiSigTx_DoubleSpendAltChainSameBlock::generate(std::vector& events) const { + TestGenerator mainChain(prepare(events)); + TestGenerator altChain(mainChain); + + mainChain.makeNextBlock(); // main chain + mainChain.addCallback("mark_last_valid_block"); + + SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, m_txsKeepedByBlock); + + auto builder = createBobToAliceTx(); + + std::list txs; + auto tx1 = builder.build(); + auto tx2 = builder.newTxKeys().build(); + txs.push_back(tx1); + txs.push_back(tx2); + + altChain.addEvent(tx1); + altChain.addEvent(tx2); + altChain.makeNextBlock(txs); + altChain.generateBlocks(); // force switch to alt chain + + mainChain.addCallback("check_double_spend"); + return true; +} + +//====================================================================================================================== +// MultiSigTx_DoubleSpendAltChainDifferentBlocks +//====================================================================================================================== + +MultiSigTx_DoubleSpendAltChainDifferentBlocks::MultiSigTx_DoubleSpendAltChainDifferentBlocks(bool txsKeepedByBlock) + : m_txsKeepedByBlock(txsKeepedByBlock) { + has_invalid_tx = !txsKeepedByBlock; +} + +bool MultiSigTx_DoubleSpendAltChainDifferentBlocks::generate(std::vector& events) const { + TestGenerator mainChain(prepare(events)); + TestGenerator altChain(mainChain); + + mainChain.makeNextBlock(); // main chain + + mainChain.addCallback("mark_last_valid_block"); + SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, m_txsKeepedByBlock); + + auto builder = createBobToAliceTx(); + + auto tx1 = builder.build(); + + altChain.addEvent(tx1); + altChain.makeNextBlock(tx1); + altChain.addCallback("mark_last_valid_block"); + + auto tx2 = builder.newTxKeys().build(); + + if (has_invalid_tx) { + altChain.addCallback("mark_invalid_tx"); + } + + altChain.addEvent(tx2); + altChain.addCallback("mark_invalid_block"); + altChain.makeNextBlock(tx2); + + mainChain.addCallback("check_double_spend"); return true; } diff --git a/tests/core_tests/double_spend.h b/tests/core_tests/double_spend.h index 26142989..ebbf6787 100644 --- a/tests/core_tests/double_spend.h +++ b/tests/core_tests/double_spend.h @@ -17,6 +17,7 @@ #pragma once #include "chaingen.h" +#include "TransactionBuilder.h" const size_t invalid_index_value = std::numeric_limits::max(); @@ -29,8 +30,8 @@ public: gen_double_spend_base(); - bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& tx); - bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& block); + bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::Transaction& tx); + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::Block& block); bool mark_last_valid_block(cryptonote::core& c, size_t ev_index, const std::vector& events); bool mark_invalid_tx(cryptonote::core& c, size_t ev_index, const std::vector& events); @@ -38,7 +39,7 @@ public: bool check_double_spend(cryptonote::core& c, size_t ev_index, const std::vector& events); private: - cryptonote::block m_last_valid_block; + cryptonote::Block m_last_valid_block; size_t m_invalid_tx_index; size_t m_invalid_block_index; }; @@ -77,11 +78,19 @@ struct gen_double_spend_in_different_blocks : public gen_double_spend_base< gen_ static const bool has_invalid_tx = !txs_keeped_by_block; static const size_t expected_pool_txs_count = has_invalid_tx ? 0 : 1; static const uint64_t expected_bob_balance = 0; - static const uint64_t expected_alice_balance = send_amount - TESTS_DEFAULT_FEE; + static uint64_t expected_alice_balance; + + gen_double_spend_in_different_blocks() : + gen_double_spend_base< gen_double_spend_in_different_blocks >() { + expected_alice_balance = send_amount - this->m_currency.minimumFee(); + } bool generate(std::vector& events) const; }; +template +uint64_t gen_double_spend_in_different_blocks::expected_alice_balance; + template struct gen_double_spend_in_alt_chain_in_the_same_block : public gen_double_spend_base< gen_double_spend_in_alt_chain_in_the_same_block > @@ -113,7 +122,7 @@ class gen_double_spend_in_different_chains : public test_chain_unit_base { public: static const uint64_t send_amount = MK_COINS(31); - static const size_t expected_blockchain_height = 4 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; + size_t expected_blockchain_height; gen_double_spend_in_different_chains(); @@ -123,6 +132,95 @@ public: }; +class TestGenerator; + +class DoubleSpendBase : public test_chain_unit_base +{ +public: + + // parameters to be checked + uint64_t send_amount; + bool has_invalid_tx; + + DoubleSpendBase(); + + bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::Transaction& tx); + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::Block& block); + + bool mark_last_valid_block(cryptonote::core& c, size_t ev_index, const std::vector& events); + bool mark_invalid_tx(cryptonote::core& c, size_t ev_index, const std::vector& events); + bool mark_invalid_block(cryptonote::core& c, size_t ev_index, const std::vector& events); + bool check_double_spend(cryptonote::core& c, size_t ev_index, const std::vector& events); + + TestGenerator prepare(std::vector& events) const; + TransactionBuilder createBobToAliceTx() const; + +protected: + + cryptonote::account_base m_bob_account; + cryptonote::account_base m_alice_account; + +private: + + crypto::hash m_last_valid_block; + size_t m_invalid_tx_index; + size_t m_invalid_block_index; +}; + + +struct MultiSigTx_DoubleSpendInTx : public DoubleSpendBase +{ + const bool m_txsKeepedByBlock; + + MultiSigTx_DoubleSpendInTx(bool txsKeepedByBlock); + + bool generate(std::vector& events) const; +}; + +struct MultiSigTx_DoubleSpendSameBlock : public DoubleSpendBase +{ + const bool m_txsKeepedByBlock; + + MultiSigTx_DoubleSpendSameBlock(bool txsKeepedByBlock); + + bool generate(std::vector& events) const; +}; + + +struct MultiSigTx_DoubleSpendDifferentBlocks : public DoubleSpendBase +{ + const bool m_txsKeepedByBlock; + + MultiSigTx_DoubleSpendDifferentBlocks(bool txsKeepedByBlock); + + bool generate(std::vector& events) const; +}; + +struct MultiSigTx_DoubleSpendAltChainSameBlock : public DoubleSpendBase +{ + const bool m_txsKeepedByBlock; + + MultiSigTx_DoubleSpendAltChainSameBlock(bool txsKeepedByBlock); + + bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::Transaction& tx) { + return true; + } + + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::Block& block) { + return true; + } + + bool generate(std::vector& events) const; +}; + +struct MultiSigTx_DoubleSpendAltChainDifferentBlocks : public DoubleSpendBase +{ + const bool m_txsKeepedByBlock; + MultiSigTx_DoubleSpendAltChainDifferentBlocks(bool txsKeepedByBlock); + bool generate(std::vector& events) const; +}; + + #define INIT_DOUBLE_SPEND_TEST() \ uint64_t ts_start = 1338224400; \ GENERATE_ACCOUNT(miner_account); \ diff --git a/tests/core_tests/double_spend.inl b/tests/core_tests/double_spend.inl index f0b895a0..aa9c2320 100644 --- a/tests/core_tests/double_spend.inl +++ b/tests/core_tests/double_spend.inl @@ -31,7 +31,7 @@ gen_double_spend_base::gen_double_spend_base() } template -bool gen_double_spend_base::check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& /*tx*/) +bool gen_double_spend_base::check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::Transaction& /*tx*/) { if (m_invalid_tx_index == event_idx) return tvc.m_verifivation_failed; @@ -40,7 +40,7 @@ bool gen_double_spend_base::check_tx_verification_context(const c } template -bool gen_double_spend_base::check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*block*/) +bool gen_double_spend_base::check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::Block& /*block*/) { if (m_invalid_block_index == event_idx) return bvc.m_verifivation_failed; @@ -51,7 +51,7 @@ bool gen_double_spend_base::check_block_verification_context(cons template bool gen_double_spend_base::mark_last_valid_block(cryptonote::core& c, size_t /*ev_index*/, const std::vector& /*events*/) { - std::list block_list; + std::list block_list; bool r = c.get_blocks(c.get_current_blockchain_height() - 1, 1, block_list); CHECK_AND_ASSERT_MES(r, false, "core::get_blocks failed"); m_last_valid_block = block_list.back(); @@ -83,8 +83,8 @@ bool gen_double_spend_base::check_double_spend(cryptonote::core& } CHECK_NOT_EQ(invalid_index_value, m_invalid_block_index); - std::list block_list; - bool r = c.get_blocks(0, 100 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, block_list); + std::list block_list; + bool r = c.get_blocks(0, 100 + 2 * this->m_currency.minedMoneyUnlockWindow(), block_list); CHECK_TEST_CONDITION(r); CHECK_TEST_CONDITION(m_last_valid_block == block_list.back()); @@ -93,9 +93,9 @@ bool gen_double_spend_base::check_double_spend(cryptonote::core& cryptonote::account_base bob_account = boost::get(events[1]); cryptonote::account_base alice_account = boost::get(events[2]); - std::vector chain; + std::vector chain; map_hash2tx_t mtx; - std::vector blocks(block_list.begin(), block_list.end()); + std::vector blocks(block_list.begin(), block_list.end()); r = find_block_chain(events, chain, mtx, get_block_hash(blocks.back())); CHECK_TEST_CONDITION(r); CHECK_EQ(concrete_test::expected_bob_balance, get_balance(bob_account, blocks, mtx)); @@ -115,7 +115,7 @@ bool gen_double_spend_in_tx::generate(std::vector sources; cryptonote::tx_source_entry se; se.amount = tx_0.vout[0].amount; - se.outputs.push_back(std::make_pair(0, boost::get(tx_0.vout[0].target).key)); + se.outputs.push_back(std::make_pair(0, boost::get(tx_0.vout[0].target).key)); se.real_output = 0; se.real_out_tx_key = get_tx_pub_key_from_extra(tx_0); se.real_output_in_tx_index = 0; @@ -125,11 +125,11 @@ bool gen_double_spend_in_tx::generate(std::vectorm_currency.minimumFee(); std::vector destinations; destinations.push_back(de); - cryptonote::transaction tx_1; + cryptonote::Transaction tx_1; if (!construct_tx(bob_account.get_keys(), sources, destinations, std::vector(), tx_1, 0)) return false; @@ -151,8 +151,8 @@ bool gen_double_spend_in_the_same_block::generate(std::vect DO_CALLBACK(events, "mark_last_valid_block"); SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, txs_keeped_by_block); - MAKE_TX_LIST_START(events, txs_1, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); - cryptonote::transaction tx_1 = txs_1.front(); + MAKE_TX_LIST_START(events, txs_1, bob_account, alice_account, send_amount - this->m_currency.minimumFee(), blk_1); + cryptonote::Transaction tx_1 = txs_1.front(); auto tx_1_idx = events.size() - 1; // Remove tx_1, it is being inserted back a little later events.pop_back(); @@ -161,7 +161,7 @@ bool gen_double_spend_in_the_same_block::generate(std::vect { DO_CALLBACK(events, "mark_invalid_tx"); } - MAKE_TX_LIST(events, txs_1, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); + MAKE_TX_LIST(events, txs_1, bob_account, alice_account, send_amount - this->m_currency.minimumFee(), blk_1); events.insert(events.begin() + tx_1_idx, tx_1); DO_CALLBACK(events, "mark_invalid_block"); MAKE_NEXT_BLOCK_TX_LIST(events, blk_2, blk_1r, miner_account, txs_1); @@ -179,9 +179,9 @@ bool gen_double_spend_in_different_blocks::generate(std::ve SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, txs_keeped_by_block); // Create two identical transactions, but don't push it to events list - MAKE_TX(events, tx_blk_2, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); + MAKE_TX(events, tx_blk_2, bob_account, alice_account, send_amount - this->m_currency.minimumFee(), blk_1); events.pop_back(); - MAKE_TX(events, tx_blk_3, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); + MAKE_TX(events, tx_blk_3, bob_account, alice_account, send_amount - this->m_currency.minimumFee(), blk_1); events.pop_back(); events.push_back(tx_blk_2); @@ -213,8 +213,8 @@ bool gen_double_spend_in_alt_chain_in_the_same_block::gener DO_CALLBACK(events, "mark_last_valid_block"); // Alt chain - MAKE_TX_LIST_START(events, txs_1, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); - cryptonote::transaction tx_1 = txs_1.front(); + MAKE_TX_LIST_START(events, txs_1, bob_account, alice_account, send_amount - this->m_currency.minimumFee(), blk_1); + cryptonote::Transaction tx_1 = txs_1.front(); auto tx_1_idx = events.size() - 1; // Remove tx_1, it is being inserted back a little later events.pop_back(); @@ -223,7 +223,7 @@ bool gen_double_spend_in_alt_chain_in_the_same_block::gener { DO_CALLBACK(events, "mark_invalid_tx"); } - MAKE_TX_LIST(events, txs_1, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); + MAKE_TX_LIST(events, txs_1, bob_account, alice_account, send_amount - this->m_currency.minimumFee(), blk_1); events.insert(events.begin() + tx_1_idx, tx_1); MAKE_NEXT_BLOCK_TX_LIST(events, blk_3, blk_1r, miner_account, txs_1); @@ -248,9 +248,9 @@ bool gen_double_spend_in_alt_chain_in_different_blocks::gen DO_CALLBACK(events, "mark_last_valid_block"); // Alternative chain - MAKE_TX(events, tx_1, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); + MAKE_TX(events, tx_1, bob_account, alice_account, send_amount - this->m_currency.minimumFee(), blk_1); events.pop_back(); - MAKE_TX(events, tx_2, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); + MAKE_TX(events, tx_2, bob_account, alice_account, send_amount - this->m_currency.minimumFee(), blk_1); events.pop_back(); events.push_back(tx_1); diff --git a/tests/core_tests/integer_overflow.cpp b/tests/core_tests/integer_overflow.cpp index 3a9edcb7..e1ddeb6f 100644 --- a/tests/core_tests/integer_overflow.cpp +++ b/tests/core_tests/integer_overflow.cpp @@ -15,9 +15,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . -#include "chaingen.h" -#include "chaingen_tests_list.h" - #include "integer_overflow.h" using namespace epee; @@ -25,30 +22,30 @@ using namespace cryptonote; namespace { - void split_miner_tx_outs(transaction& miner_tx, uint64_t amount_1) + void split_miner_tx_outs(Transaction& miner_tx, uint64_t amount_1) { uint64_t total_amount = get_outs_money_amount(miner_tx); uint64_t amount_2 = total_amount - amount_1; - txout_target_v target = miner_tx.vout[0].target; + TransactionOutputTarget target = miner_tx.vout[0].target; miner_tx.vout.clear(); - tx_out out1; + TransactionOutput out1; out1.amount = amount_1; out1.target = target; miner_tx.vout.push_back(out1); - tx_out out2; + TransactionOutput out2; out2.amount = amount_2; out2.target = target; miner_tx.vout.push_back(out2); } - void append_tx_source_entry(std::vector& sources, const transaction& tx, size_t out_idx) + void append_tx_source_entry(std::vector& sources, const Transaction& tx, size_t out_idx) { cryptonote::tx_source_entry se; se.amount = tx.vout[out_idx].amount; - se.outputs.push_back(std::make_pair(0, boost::get(tx.vout[out_idx].target).key)); + se.outputs.push_back(std::make_pair(0, boost::get(tx.vout[out_idx].target).key)); se.real_output = 0; se.real_out_tx_key = get_tx_pub_key_from_extra(tx); se.real_output_in_tx_index = out_idx; @@ -65,12 +62,12 @@ gen_uint_overflow_base::gen_uint_overflow_base() REGISTER_CALLBACK_METHOD(gen_uint_overflow_1, mark_last_valid_block); } -bool gen_uint_overflow_base::check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& /*tx*/) +bool gen_uint_overflow_base::check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::Transaction& /*tx*/) { return m_last_valid_block_event_idx < event_idx ? !tx_added && tvc.m_verifivation_failed : tx_added && !tvc.m_verifivation_failed; } -bool gen_uint_overflow_base::check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*block*/) +bool gen_uint_overflow_base::check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::Block& /*block*/) { return m_last_valid_block_event_idx < event_idx ? bvc.m_verifivation_failed | bvc.m_marked_as_orphaned : !bvc.m_verifivation_failed; } @@ -95,31 +92,31 @@ bool gen_uint_overflow_1::generate(std::vector& events) const // Problem 1. Miner tx output overflow MAKE_MINER_TX_MANUALLY(miner_tx_0, blk_0); - split_miner_tx_outs(miner_tx_0, MONEY_SUPPLY); - block blk_1; - if (!generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx_0)) + split_miner_tx_outs(miner_tx_0, m_currency.moneySupply()); + Block blk_1; + if (!generator.constructBlockManually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx_0)) return false; events.push_back(blk_1); // Problem 1. Miner tx outputs overflow MAKE_MINER_TX_MANUALLY(miner_tx_1, blk_1); - split_miner_tx_outs(miner_tx_1, MONEY_SUPPLY); - block blk_2; - if (!generator.construct_block_manually(blk_2, blk_1, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx_1)) + split_miner_tx_outs(miner_tx_1, m_currency.moneySupply()); + Block blk_2; + if (!generator.constructBlockManually(blk_2, blk_1, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx_1)) return false; events.push_back(blk_2); REWIND_BLOCKS(events, blk_2r, blk_2, miner_account); - MAKE_TX_LIST_START(events, txs_0, miner_account, bob_account, MONEY_SUPPLY, blk_2); - MAKE_TX_LIST(events, txs_0, miner_account, bob_account, MONEY_SUPPLY, blk_2); + MAKE_TX_LIST_START(events, txs_0, miner_account, bob_account, m_currency.moneySupply(), blk_2); + MAKE_TX_LIST(events, txs_0, miner_account, bob_account, m_currency.moneySupply(), blk_2); MAKE_NEXT_BLOCK_TX_LIST(events, blk_3, blk_2r, miner_account, txs_0); REWIND_BLOCKS(events, blk_3r, blk_3, miner_account); // Problem 2. total_fee overflow, block_reward overflow - std::list txs_1; + std::list txs_1; // Create txs with huge fee - txs_1.push_back(construct_tx_with_fee(events, blk_3, bob_account, alice_account, MK_COINS(1), MONEY_SUPPLY - MK_COINS(1))); - txs_1.push_back(construct_tx_with_fee(events, blk_3, bob_account, alice_account, MK_COINS(1), MONEY_SUPPLY - MK_COINS(1))); + txs_1.push_back(construct_tx_with_fee(events, blk_3, bob_account, alice_account, MK_COINS(1), m_currency.moneySupply() - MK_COINS(1))); + txs_1.push_back(construct_tx_with_fee(events, blk_3, bob_account, alice_account, MK_COINS(1), m_currency.moneySupply() - MK_COINS(1))); MAKE_NEXT_BLOCK_TX_LIST(events, blk_4, blk_3r, miner_account, txs_1); return true; @@ -140,11 +137,11 @@ bool gen_uint_overflow_2::generate(std::vector& events) const // Problem 1. Regular tx outputs overflow std::vector sources; - for (size_t i = 0; i < blk_0.miner_tx.vout.size(); ++i) + for (size_t i = 0; i < blk_0.minerTx.vout.size(); ++i) { - if (TESTS_DEFAULT_FEE < blk_0.miner_tx.vout[i].amount) + if (m_currency.minimumFee() < blk_0.minerTx.vout[i].amount) { - append_tx_source_entry(sources, blk_0.miner_tx, i); + append_tx_source_entry(sources, blk_0.minerTx, i); break; } } @@ -154,13 +151,13 @@ bool gen_uint_overflow_2::generate(std::vector& events) const } std::vector destinations; - const account_public_address& bob_addr = bob_account.get_keys().m_account_address; - destinations.push_back(tx_destination_entry(MONEY_SUPPLY, bob_addr)); - destinations.push_back(tx_destination_entry(MONEY_SUPPLY - 1, bob_addr)); - // sources.front().amount = destinations[0].amount + destinations[2].amount + destinations[3].amount + TESTS_DEFAULT_FEE - destinations.push_back(tx_destination_entry(sources.front().amount - MONEY_SUPPLY - MONEY_SUPPLY + 1 - TESTS_DEFAULT_FEE, bob_addr)); + const AccountPublicAddress& bob_addr = bob_account.get_keys().m_account_address; + destinations.push_back(tx_destination_entry(m_currency.moneySupply(), bob_addr)); + destinations.push_back(tx_destination_entry(m_currency.moneySupply() - 1, bob_addr)); + // sources.front().amount = destinations[0].amount + destinations[2].amount + destinations[3].amount + m_currency.minimumFee() + destinations.push_back(tx_destination_entry(sources.front().amount - m_currency.moneySupply() - m_currency.moneySupply() + 1 - m_currency.minimumFee(), bob_addr)); - cryptonote::transaction tx_1; + cryptonote::Transaction tx_1; if (!construct_tx(miner_account.get_keys(), sources, destinations, std::vector(), tx_1, 0)) return false; events.push_back(tx_1); @@ -173,7 +170,7 @@ bool gen_uint_overflow_2::generate(std::vector& events) const for (size_t i = 0; i < tx_1.vout.size(); ++i) { auto& tx_1_out = tx_1.vout[i]; - if (tx_1_out.amount < MONEY_SUPPLY - 1) + if (tx_1_out.amount < m_currency.moneySupply() - 1) continue; append_tx_source_entry(sources, tx_1, i); @@ -182,11 +179,11 @@ bool gen_uint_overflow_2::generate(std::vector& events) const destinations.clear(); cryptonote::tx_destination_entry de; de.addr = alice_account.get_keys().m_account_address; - de.amount = MONEY_SUPPLY - TESTS_DEFAULT_FEE; + de.amount = m_currency.moneySupply() - m_currency.minimumFee(); destinations.push_back(de); destinations.push_back(de); - cryptonote::transaction tx_2; + cryptonote::Transaction tx_2; if (!construct_tx(bob_account.get_keys(), sources, destinations, std::vector(), tx_2, 0)) return false; events.push_back(tx_2); diff --git a/tests/core_tests/integer_overflow.h b/tests/core_tests/integer_overflow.h index 39ef73d3..368ac4de 100644 --- a/tests/core_tests/integer_overflow.h +++ b/tests/core_tests/integer_overflow.h @@ -22,8 +22,8 @@ struct gen_uint_overflow_base : public test_chain_unit_base { gen_uint_overflow_base(); - bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& tx); - bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& block); + bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::Transaction& tx); + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::Block& block); bool mark_last_valid_block(cryptonote::core& c, size_t ev_index, const std::vector& events); diff --git a/tests/core_tests/ring_signature_1.cpp b/tests/core_tests/ring_signature_1.cpp index 09698cc9..b30598ff 100644 --- a/tests/core_tests/ring_signature_1.cpp +++ b/tests/core_tests/ring_signature_1.cpp @@ -15,8 +15,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . -#include "chaingen.h" -#include "chaingen_tests_list.h" +#include "ring_signature_1.h" using namespace epee; using namespace cryptonote; @@ -74,7 +73,7 @@ bool gen_ring_signature_1::generate(std::vector& events) const DO_CALLBACK(events, "check_balances_1"); // 23 + 2N REWIND_BLOCKS(events, blk_6r, blk_6, miner_account); // // 129 = 11 + 11 + 20 + 29 + 29 + 29 - MAKE_TX_MIX(events, tx_0, bob_account, alice_account, MK_COINS(129) + 2 * rnd_11 + rnd_20 + 3 * rnd_29 - TESTS_DEFAULT_FEE, 2, blk_6); // 24 + 3N + MAKE_TX_MIX(events, tx_0, bob_account, alice_account, MK_COINS(129) + 2 * rnd_11 + rnd_20 + 3 * rnd_29 - m_currency.minimumFee(), 2, blk_6); // 24 + 3N MAKE_NEXT_BLOCK_TX1(events, blk_7, blk_6r, miner_account, tx_0); // 25 + 3N DO_CALLBACK(events, "check_balances_2"); // 26 + 3N @@ -88,11 +87,11 @@ bool gen_ring_signature_1::check_balances_1(cryptonote::core& c, size_t ev_index m_bob_account = boost::get(events[3]); m_alice_account = boost::get(events[4]); - std::list blocks; - bool r = c.get_blocks(0, 100 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks); + std::list blocks; + bool r = c.get_blocks(0, 100 + 2 * m_currency.minedMoneyUnlockWindow(), blocks); CHECK_TEST_CONDITION(r); - std::vector chain; + std::vector chain; map_hash2tx_t mtx; r = find_block_chain(events, chain, mtx, get_block_hash(blocks.back())); CHECK_TEST_CONDITION(r); @@ -106,16 +105,16 @@ bool gen_ring_signature_1::check_balances_2(cryptonote::core& c, size_t ev_index { DEFINE_TESTS_ERROR_CONTEXT("gen_ring_signature_1::check_balances_2"); - std::list blocks; - bool r = c.get_blocks(0, 100 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks); + std::list blocks; + bool r = c.get_blocks(0, 100 + 2 * m_currency.minedMoneyUnlockWindow(), blocks); CHECK_TEST_CONDITION(r); - std::vector chain; + std::vector chain; map_hash2tx_t mtx; r = find_block_chain(events, chain, mtx, get_block_hash(blocks.back())); CHECK_TEST_CONDITION(r); CHECK_EQ(MK_COINS(1), get_balance(m_bob_account, chain, mtx)); - CHECK_EQ(MK_COINS(129) + 2 * rnd_11 + rnd_20 + 3 * rnd_29 - TESTS_DEFAULT_FEE, get_balance(m_alice_account, chain, mtx)); + CHECK_EQ(MK_COINS(129) + 2 * rnd_11 + rnd_20 + 3 * rnd_29 - m_currency.minimumFee(), get_balance(m_alice_account, chain, mtx)); return true; } @@ -155,7 +154,7 @@ bool gen_ring_signature_2::generate(std::vector& events) const MAKE_NEXT_BLOCK_TX_LIST(events, blk_4, blk_3r, miner_account, txs_blk_4); // 10 + N DO_CALLBACK(events, "check_balances_1"); // 11 + N REWIND_BLOCKS(events, blk_4r, blk_4, miner_account); // - MAKE_TX_MIX(events, tx_0, bob_account, alice_account, MK_COINS(244) - TESTS_DEFAULT_FEE, 3, blk_4); // 12 + 2N + MAKE_TX_MIX(events, tx_0, bob_account, alice_account, MK_COINS(244) - m_currency.minimumFee(), 3, blk_4); // 12 + 2N MAKE_NEXT_BLOCK_TX1(events, blk_5, blk_4r, miner_account, tx_0); // 13 + 2N DO_CALLBACK(events, "check_balances_2"); // 14 + 2N @@ -169,11 +168,11 @@ bool gen_ring_signature_2::check_balances_1(cryptonote::core& c, size_t ev_index m_bob_account = boost::get(events[1]); m_alice_account = boost::get(events[2]); - std::list blocks; - bool r = c.get_blocks(0, 100 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks); + std::list blocks; + bool r = c.get_blocks(0, 100 + 2 * m_currency.minedMoneyUnlockWindow(), blocks); CHECK_TEST_CONDITION(r); - std::vector chain; + std::vector chain; map_hash2tx_t mtx; r = find_block_chain(events, chain, mtx, get_block_hash(blocks.back())); CHECK_TEST_CONDITION(r); @@ -187,16 +186,16 @@ bool gen_ring_signature_2::check_balances_2(cryptonote::core& c, size_t ev_index { DEFINE_TESTS_ERROR_CONTEXT("gen_ring_signature_2::check_balances_2"); - std::list blocks; - bool r = c.get_blocks(0, 100 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks); + std::list blocks; + bool r = c.get_blocks(0, 100 + 2 * m_currency.minedMoneyUnlockWindow(), blocks); CHECK_TEST_CONDITION(r); - std::vector chain; + std::vector chain; map_hash2tx_t mtx; r = find_block_chain(events, chain, mtx, get_block_hash(blocks.back())); CHECK_TEST_CONDITION(r); CHECK_EQ(0, get_balance(m_bob_account, chain, mtx)); - CHECK_EQ(MK_COINS(244) - TESTS_DEFAULT_FEE, get_balance(m_alice_account, chain, mtx)); + CHECK_EQ(MK_COINS(244) - m_currency.minimumFee(), get_balance(m_alice_account, chain, mtx)); return true; } @@ -223,8 +222,8 @@ gen_ring_signature_big::gen_ring_signature_big() bool gen_ring_signature_big::generate(std::vector& events) const { std::vector accounts(m_test_size); - std::vector blocks; - blocks.reserve(m_test_size + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW); + std::vector blocks; + blocks.reserve(m_test_size + m_currency.minedMoneyUnlockWindow()); uint64_t ts_start = 1338224400; GENERATE_ACCOUNT(miner_account); @@ -243,21 +242,21 @@ bool gen_ring_signature_big::generate(std::vector& events) con blocks.push_back(blk_0); for (size_t i = blk_0r_idx; i < events.size(); ++i) { - blocks.push_back(boost::get(events[i])); + blocks.push_back(boost::get(events[i])); } for (size_t i = 0; i < m_test_size; ++i) { - block blk_with_unlocked_out = blocks[blocks.size() - 1 - CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW]; + Block blk_with_unlocked_out = blocks[blocks.size() - 1 - m_currency.minedMoneyUnlockWindow()]; MAKE_TX_LIST_START(events, txs_blk_i, miner_account, accounts[i], m_tx_amount, blk_with_unlocked_out); for (size_t j = 0; j <= i; ++j) { - MAKE_TX_LIST(events, txs_blk_i, miner_account, accounts[i], TESTS_DEFAULT_FEE, blk_with_unlocked_out); + MAKE_TX_LIST(events, txs_blk_i, miner_account, accounts[i], m_currency.minimumFee(), blk_with_unlocked_out); } MAKE_NEXT_BLOCK_TX_LIST(events, blk_i, blocks.back(), miner_account, txs_blk_i); blocks.push_back(blk_i); - std::vector chain; + std::vector chain; map_hash2tx_t mtx; bool r = find_block_chain(events, chain, mtx, get_block_hash(blk_i)); CHECK_AND_NO_ASSERT_MES(r, false, "failed to call find_block_chain"); @@ -279,21 +278,21 @@ bool gen_ring_signature_big::check_balances_1(cryptonote::core& c, size_t ev_ind m_bob_account = boost::get(events[1]); m_alice_account = boost::get(events[1 + m_test_size]); - std::list blocks; - bool r = c.get_blocks(0, 2 * m_test_size + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks); + std::list blocks; + bool r = c.get_blocks(0, 2 * m_test_size + m_currency.minedMoneyUnlockWindow(), blocks); CHECK_TEST_CONDITION(r); - std::vector chain; + std::vector chain; map_hash2tx_t mtx; r = find_block_chain(events, chain, mtx, get_block_hash(blocks.back())); CHECK_TEST_CONDITION(r); - CHECK_EQ(m_tx_amount + TESTS_DEFAULT_FEE, get_balance(m_bob_account, chain, mtx)); + CHECK_EQ(m_tx_amount + m_currency.minimumFee(), get_balance(m_bob_account, chain, mtx)); CHECK_EQ(0, get_balance(m_alice_account, chain, mtx)); for (size_t i = 2; i < 1 + m_test_size; ++i) { const account_base& an_account = boost::get(events[i]); - uint64_t balance = m_tx_amount + TESTS_DEFAULT_FEE * i; + uint64_t balance = m_tx_amount + m_currency.minimumFee() * i; CHECK_EQ(balance, get_balance(an_account, chain, mtx)); } @@ -304,11 +303,11 @@ bool gen_ring_signature_big::check_balances_2(cryptonote::core& c, size_t ev_ind { DEFINE_TESTS_ERROR_CONTEXT("gen_ring_signature_big::check_balances_2"); - std::list blocks; - bool r = c.get_blocks(0, 2 * m_test_size + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks); + std::list blocks; + bool r = c.get_blocks(0, 2 * m_test_size + m_currency.minedMoneyUnlockWindow(), blocks); CHECK_TEST_CONDITION(r); - std::vector chain; + std::vector chain; map_hash2tx_t mtx; r = find_block_chain(events, chain, mtx, get_block_hash(blocks.back())); CHECK_TEST_CONDITION(r); @@ -318,13 +317,13 @@ bool gen_ring_signature_big::check_balances_2(cryptonote::core& c, size_t ev_ind for (size_t i = 2; i < 1 + m_test_size; ++i) { const account_base& an_account = boost::get(events[i]); - uint64_t balance = m_tx_amount + TESTS_DEFAULT_FEE * i; + uint64_t balance = m_tx_amount + m_currency.minimumFee() * i; CHECK_EQ(balance, get_balance(an_account, chain, mtx)); } std::vector tx_outs; uint64_t transfered; - lookup_acc_outs(m_alice_account.get_keys(), boost::get(events[events.size() - 3]), get_tx_pub_key_from_extra(boost::get(events[events.size() - 3])), tx_outs, transfered); + lookup_acc_outs(m_alice_account.get_keys(), boost::get(events[events.size() - 3]), get_tx_pub_key_from_extra(boost::get(events[events.size() - 3])), tx_outs, transfered); CHECK_EQ(m_tx_amount, transfered); return true; diff --git a/tests/core_tests/transaction_tests.cpp b/tests/core_tests/transaction_tests.cpp index dbf41fa2..5085aa6f 100644 --- a/tests/core_tests/transaction_tests.cpp +++ b/tests/core_tests/transaction_tests.cpp @@ -19,14 +19,14 @@ #include "cryptonote_core/cryptonote_basic_impl.h" #include "cryptonote_core/account.h" #include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_core/Currency.h" #include "misc_language.h" using namespace cryptonote; - - bool test_transaction_generation_and_ring_signature() { + cryptonote::Currency currency = cryptonote::CurrencyBuilder().currency(); account_base miner_acc1; miner_acc1.generate(); @@ -41,25 +41,24 @@ bool test_transaction_generation_and_ring_signature() account_base miner_acc6; miner_acc6.generate(); - std::string add_str = miner_acc3.get_public_address_str(); - + std::string add_str = currency.accountAddressAsString(miner_acc3); account_base rv_acc; rv_acc.generate(); account_base rv_acc2; rv_acc2.generate(); - transaction tx_mine_1; - construct_miner_tx(0, 0, 0, 10, 0, miner_acc1.get_keys().m_account_address, tx_mine_1); - transaction tx_mine_2; - construct_miner_tx(0, 0, 0, 0, 0, miner_acc2.get_keys().m_account_address, tx_mine_2); - transaction tx_mine_3; - construct_miner_tx(0, 0, 0, 0, 0, miner_acc3.get_keys().m_account_address, tx_mine_3); - transaction tx_mine_4; - construct_miner_tx(0, 0, 0, 0, 0, miner_acc4.get_keys().m_account_address, tx_mine_4); - transaction tx_mine_5; - construct_miner_tx(0, 0, 0, 0, 0, miner_acc5.get_keys().m_account_address, tx_mine_5); - transaction tx_mine_6; - construct_miner_tx(0, 0, 0, 0, 0, miner_acc6.get_keys().m_account_address, tx_mine_6); + Transaction tx_mine_1; + currency.constructMinerTx(0, 0, 0, 10, 0, miner_acc1.get_keys().m_account_address, tx_mine_1); + Transaction tx_mine_2; + currency.constructMinerTx(0, 0, 0, 0, 0, miner_acc2.get_keys().m_account_address, tx_mine_2); + Transaction tx_mine_3; + currency.constructMinerTx(0, 0, 0, 0, 0, miner_acc3.get_keys().m_account_address, tx_mine_3); + Transaction tx_mine_4; + currency.constructMinerTx(0, 0, 0, 0, 0, miner_acc4.get_keys().m_account_address, tx_mine_4); + Transaction tx_mine_5; + currency.constructMinerTx(0, 0, 0, 0, 0, miner_acc5.get_keys().m_account_address, tx_mine_5); + Transaction tx_mine_6; + currency.constructMinerTx(0, 0, 0, 0, 0, miner_acc6.get_keys().m_account_address, tx_mine_6); //fill inputs entry typedef tx_source_entry::output_entry tx_output_entry; @@ -70,27 +69,27 @@ bool test_transaction_generation_and_ring_signature() { tx_output_entry oe; oe.first = 0; - oe.second = boost::get(tx_mine_1.vout[0].target).key; + oe.second = boost::get(tx_mine_1.vout[0].target).key; src.outputs.push_back(oe); oe.first = 1; - oe.second = boost::get(tx_mine_2.vout[0].target).key; + oe.second = boost::get(tx_mine_2.vout[0].target).key; src.outputs.push_back(oe); oe.first = 2; - oe.second = boost::get(tx_mine_3.vout[0].target).key; + oe.second = boost::get(tx_mine_3.vout[0].target).key; src.outputs.push_back(oe); oe.first = 3; - oe.second = boost::get(tx_mine_4.vout[0].target).key; + oe.second = boost::get(tx_mine_4.vout[0].target).key; src.outputs.push_back(oe); oe.first = 4; - oe.second = boost::get(tx_mine_5.vout[0].target).key; + oe.second = boost::get(tx_mine_5.vout[0].target).key; src.outputs.push_back(oe); oe.first = 5; - oe.second = boost::get(tx_mine_6.vout[0].target).key; + oe.second = boost::get(tx_mine_6.vout[0].target).key; src.outputs.push_back(oe); src.real_out_tx_key = cryptonote::get_tx_pub_key_from_extra(tx_mine_2); @@ -104,19 +103,20 @@ bool test_transaction_generation_and_ring_signature() std::vector destinations; destinations.push_back(td); - transaction tx_rc1; + Transaction tx_rc1; bool r = construct_tx(miner_acc2.get_keys(), sources, destinations, std::vector(), tx_rc1, 0); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); crypto::hash pref_hash = get_transaction_prefix_hash(tx_rc1); std::vector output_keys; - output_keys.push_back(&boost::get(tx_mine_1.vout[0].target).key); - output_keys.push_back(&boost::get(tx_mine_2.vout[0].target).key); - output_keys.push_back(&boost::get(tx_mine_3.vout[0].target).key); - output_keys.push_back(&boost::get(tx_mine_4.vout[0].target).key); - output_keys.push_back(&boost::get(tx_mine_5.vout[0].target).key); - output_keys.push_back(&boost::get(tx_mine_6.vout[0].target).key); - r = crypto::check_ring_signature(pref_hash, boost::get(tx_rc1.vin[0]).k_image, output_keys, &tx_rc1.signatures[0][0]); + output_keys.push_back(&boost::get(tx_mine_1.vout[0].target).key); + output_keys.push_back(&boost::get(tx_mine_2.vout[0].target).key); + output_keys.push_back(&boost::get(tx_mine_3.vout[0].target).key); + output_keys.push_back(&boost::get(tx_mine_4.vout[0].target).key); + output_keys.push_back(&boost::get(tx_mine_5.vout[0].target).key); + output_keys.push_back(&boost::get(tx_mine_6.vout[0].target).key); + r = crypto::check_ring_signature(pref_hash, boost::get(tx_rc1.vin[0]).keyImage, + output_keys, &tx_rc1.signatures[0][0]); CHECK_AND_ASSERT_MES(r, false, "failed to check ring signature"); std::vector outs; @@ -136,11 +136,13 @@ bool test_block_creation() { uint64_t vszs[] = {80,476,476,475,475,474,475,474,474,475,472,476,476,475,475,474,475,474,474,475,472,476,476,475,475,474,475,474,474,475,9391,476,476,475,475,474,475,8819,8301,475,472,4302,5316,14347,16620,19583,19403,19728,19442,19852,19015,19000,19016,19795,19749,18087,19787,19704,19750,19267,19006,19050,19445,19407,19522,19546,19788,19369,19486,19329,19370,18853,19600,19110,19320,19746,19474,19474,19743,19494,19755,19715,19769,19620,19368,19839,19532,23424,28287,30707}; std::vector szs(&vszs[0], &vszs[90]); - account_public_address adr; - bool r = get_account_address_from_str(adr, "0099be99c70ef10fd534c43c88e9d13d1c8853213df7e362afbec0e4ee6fec4948d0c190b58f4b356cd7feaf8d9d0a76e7c7e5a9a0a497a6b1faf7a765882dd08ac2"); + cryptonote::Currency currency = cryptonote::CurrencyBuilder().currency(); + + AccountPublicAddress adr; + bool r = currency.parseAccountAddressString("272xWzbWsP4cfNFfxY5ETN5moU8x81PKfWPwynrrqsNGDBQGLmD1kCkKCvPeDUXu5XfmZkCrQ53wsWmdfvHBGLNjGcRiDcK", adr); CHECK_AND_ASSERT_MES(r, false, "failed to import"); - block b; - r = construct_miner_tx(90, epee::misc_utils::median(szs), 3553616528562147, 33094, 10000000, adr, b.miner_tx, blobdata(), 11); + Block b; + r = currency.constructMinerTx(90, epee::misc_utils::median(szs), 3553616528562147, 33094, 10000000, adr, b.minerTx, blobdata(), 11); return r; } diff --git a/tests/core_tests/transaction_tests.h b/tests/core_tests/transaction_tests.h index f46c316c..40303a3f 100644 --- a/tests/core_tests/transaction_tests.h +++ b/tests/core_tests/transaction_tests.h @@ -17,7 +17,5 @@ #pragma once - - bool test_transactions(); bool test_block_creation(); diff --git a/tests/core_tests/tx_validation.cpp b/tests/core_tests/tx_validation.cpp index 1dbfa9d8..38cc5c14 100644 --- a/tests/core_tests/tx_validation.cpp +++ b/tests/core_tests/tx_validation.cpp @@ -15,8 +15,8 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . -#include "chaingen.h" -#include "chaingen_tests_list.h" +#include "tx_validation.h" +#include "TestGenerator.h" using namespace epee; using namespace crypto; @@ -33,9 +33,9 @@ namespace m_tx.signatures.clear(); m_tx.version = version; - m_tx.unlock_time = unlock_time; + m_tx.unlockTime = unlock_time; - m_tx_key = keypair::generate(); + m_tx_key = KeyPair::generate(); add_tx_pub_key_to_extra(m_tx, m_tx_key.pub); } @@ -43,21 +43,21 @@ namespace { BOOST_FOREACH(const tx_source_entry& src_entr, sources) { - m_in_contexts.push_back(keypair()); - keypair& in_ephemeral = m_in_contexts.back(); + m_in_contexts.push_back(KeyPair()); + KeyPair& in_ephemeral = m_in_contexts.back(); crypto::key_image img; generate_key_image_helper(sender_account_keys, src_entr.real_out_tx_key, src_entr.real_output_in_tx_index, in_ephemeral, img); // put key image into tx input - txin_to_key input_to_key; + TransactionInputToKey input_to_key; input_to_key.amount = src_entr.amount; - input_to_key.k_image = img; + input_to_key.keyImage = img; // fill outputs array and use relative offsets BOOST_FOREACH(const tx_source_entry::output_entry& out_entry, src_entr.outputs) - input_to_key.key_offsets.push_back(out_entry.first); + input_to_key.keyOffsets.push_back(out_entry.first); - input_to_key.key_offsets = absolute_output_offsets_to_relative(input_to_key.key_offsets); + input_to_key.keyOffsets = absolute_output_offsets_to_relative(input_to_key.keyOffsets); m_tx.vin.push_back(input_to_key); } } @@ -69,12 +69,12 @@ namespace { crypto::key_derivation derivation; crypto::public_key out_eph_public_key; - crypto::generate_key_derivation(dst_entr.addr.m_view_public_key, m_tx_key.sec, derivation); - crypto::derive_public_key(derivation, output_index, dst_entr.addr.m_spend_public_key, out_eph_public_key); + crypto::generate_key_derivation(dst_entr.addr.m_viewPublicKey, m_tx_key.sec, derivation); + crypto::derive_public_key(derivation, output_index, dst_entr.addr.m_spendPublicKey, out_eph_public_key); - tx_out out; + TransactionOutput out; out.amount = dst_entr.amount; - txout_to_key tk; + TransactionOutputToKey tk; tk.key = out_eph_public_key; out.target = tk; m_tx.vout.push_back(out); @@ -103,24 +103,25 @@ namespace m_tx.signatures.push_back(std::vector()); std::vector& sigs = m_tx.signatures.back(); sigs.resize(src_entr.outputs.size()); - generate_ring_signature(m_tx_prefix_hash, boost::get(m_tx.vin[i]).k_image, keys_ptrs, m_in_contexts[i].sec, src_entr.real_output, sigs.data()); + generate_ring_signature(m_tx_prefix_hash, boost::get(m_tx.vin[i]).keyImage, + keys_ptrs, m_in_contexts[i].sec, src_entr.real_output, sigs.data()); i++; } } - transaction m_tx; - keypair m_tx_key; - std::vector m_in_contexts; + Transaction m_tx; + KeyPair m_tx_key; + std::vector m_in_contexts; crypto::hash m_tx_prefix_hash; }; - transaction make_simple_tx_with_unlock_time(const std::vector& events, - const cryptonote::block& blk_head, const cryptonote::account_base& from, const cryptonote::account_base& to, - uint64_t amount, uint64_t unlock_time) + Transaction make_simple_tx_with_unlock_time(const std::vector& events, + const cryptonote::Block& blk_head, const cryptonote::account_base& from, const cryptonote::account_base& to, + uint64_t amount, uint64_t fee, uint64_t unlock_time) { std::vector sources; std::vector destinations; - fill_tx_sources_and_destinations(events, blk_head, from, to, amount, TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_head, from, to, amount, fee, 0, sources, destinations); tx_builder builder; builder.step1_init(CURRENT_TRANSACTION_VERSION, unlock_time); @@ -161,7 +162,7 @@ bool gen_tx_big_version::generate(std::vector& events) const std::vector sources; std::vector destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), m_currency.minimumFee(), 0, sources, destinations); tx_builder builder; builder.step1_init(CURRENT_TRANSACTION_VERSION + 1, 0); @@ -185,12 +186,13 @@ bool gen_tx_unlock_time::generate(std::vector& events) const REWIND_BLOCKS_N(events, blk_1, blk_0, miner_account, 10); REWIND_BLOCKS(events, blk_1r, blk_1, miner_account); - auto make_tx_with_unlock_time = [&](uint64_t unlock_time) -> transaction + auto make_tx_with_unlock_time = [&](uint64_t unlock_time) -> Transaction { - return make_simple_tx_with_unlock_time(events, blk_1, miner_account, miner_account, MK_COINS(1), unlock_time); + return make_simple_tx_with_unlock_time(events, blk_1, miner_account, miner_account, MK_COINS(1), + m_currency.minimumFee(), unlock_time); }; - std::list txs_0; + std::list txs_0; txs_0.push_back(make_tx_with_unlock_time(0)); events.push_back(txs_0.back()); @@ -218,42 +220,6 @@ bool gen_tx_unlock_time::generate(std::vector& events) const return true; } -bool gen_tx_input_is_not_txin_to_key::generate(std::vector& events) const -{ - uint64_t ts_start = 1338224400; - - GENERATE_ACCOUNT(miner_account); - MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); - REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); - - MAKE_NEXT_BLOCK(events, blk_tmp, blk_0r, miner_account); - events.pop_back(); - - DO_CALLBACK(events, "mark_invalid_tx"); - events.push_back(blk_tmp.miner_tx); - - auto make_tx_with_input = [&](const txin_v& tx_input) -> transaction - { - std::vector sources; - std::vector destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); - - tx_builder builder; - builder.step1_init(); - builder.m_tx.vin.push_back(tx_input); - builder.step3_fill_outputs(destinations); - return builder.m_tx; - }; - - DO_CALLBACK(events, "mark_invalid_tx"); - events.push_back(make_tx_with_input(txin_to_script())); - - DO_CALLBACK(events, "mark_invalid_tx"); - events.push_back(make_tx_with_input(txin_to_scripthash())); - - return true; -} - bool gen_tx_no_inputs_no_outputs::generate(std::vector& events) const { uint64_t ts_start = 1338224400; @@ -279,7 +245,7 @@ bool gen_tx_no_inputs_has_outputs::generate(std::vector& event std::vector sources; std::vector destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), m_currency.minimumFee(), 0, sources, destinations); tx_builder builder; builder.step1_init(); @@ -301,7 +267,7 @@ bool gen_tx_has_inputs_no_outputs::generate(std::vector& event std::vector sources; std::vector destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), m_currency.minimumFee(), 0, sources, destinations); destinations.clear(); tx_builder builder; @@ -327,7 +293,7 @@ bool gen_tx_invalid_input_amount::generate(std::vector& events std::vector sources; std::vector destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), m_currency.minimumFee(), 0, sources, destinations); sources.front().amount++; tx_builder builder; @@ -343,7 +309,7 @@ bool gen_tx_invalid_input_amount::generate(std::vector& events return true; } -bool gen_tx_input_wo_key_offsets::generate(std::vector& events) const +bool gen_tx_in_to_key_wo_key_offsets::generate(std::vector& events) const { uint64_t ts_start = 1338224400; @@ -353,20 +319,20 @@ bool gen_tx_input_wo_key_offsets::generate(std::vector& events std::vector sources; std::vector destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), m_currency.minimumFee(), 0, sources, destinations); tx_builder builder; builder.step1_init(); builder.step2_fill_inputs(miner_account.get_keys(), sources); builder.step3_fill_outputs(destinations); - txin_to_key& in_to_key = boost::get(builder.m_tx.vin.front()); - uint64_t key_offset = in_to_key.key_offsets.front(); - in_to_key.key_offsets.pop_back(); - CHECK_AND_ASSERT_MES(in_to_key.key_offsets.empty(), false, "txin contained more than one key_offset"); + TransactionInputToKey& in_to_key = boost::get(builder.m_tx.vin.front()); + uint64_t key_offset = in_to_key.keyOffsets.front(); + in_to_key.keyOffsets.pop_back(); + CHECK_AND_ASSERT_MES(in_to_key.keyOffsets.empty(), false, "txin contained more than one key_offset"); builder.step4_calc_hash(); - in_to_key.key_offsets.push_back(key_offset); + in_to_key.keyOffsets.push_back(key_offset); builder.step5_sign(sources); - in_to_key.key_offsets.pop_back(); + in_to_key.keyOffsets.pop_back(); DO_CALLBACK(events, "mark_invalid_tx"); events.push_back(builder.m_tx); @@ -390,17 +356,17 @@ bool gen_tx_key_offest_points_to_foreign_key::generate(std::vector sources_bob; std::vector destinations_bob; - fill_tx_sources_and_destinations(events, blk_2, bob_account, miner_account, MK_COINS(60) + 1 - TESTS_DEFAULT_FEE, TESTS_DEFAULT_FEE, 0, sources_bob, destinations_bob); + fill_tx_sources_and_destinations(events, blk_2, bob_account, miner_account, MK_COINS(60) + 1 - m_currency.minimumFee(), m_currency.minimumFee(), 0, sources_bob, destinations_bob); std::vector sources_alice; std::vector destinations_alice; - fill_tx_sources_and_destinations(events, blk_2, alice_account, miner_account, MK_COINS(60) + 1 - TESTS_DEFAULT_FEE, TESTS_DEFAULT_FEE, 0, sources_alice, destinations_alice); + fill_tx_sources_and_destinations(events, blk_2, alice_account, miner_account, MK_COINS(60) + 1 - m_currency.minimumFee(), m_currency.minimumFee(), 0, sources_alice, destinations_alice); tx_builder builder; builder.step1_init(); builder.step2_fill_inputs(bob_account.get_keys(), sources_bob); - txin_to_key& in_to_key = boost::get(builder.m_tx.vin.front()); - in_to_key.key_offsets.front() = sources_alice.front().outputs.front().first; + TransactionInputToKey& in_to_key = boost::get(builder.m_tx.vin.front()); + in_to_key.keyOffsets.front() = sources_alice.front().outputs.front().first; builder.step3_fill_outputs(destinations_bob); builder.step4_calc_hash(); builder.step5_sign(sources_bob); @@ -421,13 +387,13 @@ bool gen_tx_sender_key_offest_not_exist::generate(std::vector& std::vector sources; std::vector destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), m_currency.minimumFee(), 0, sources, destinations); tx_builder builder; builder.step1_init(); builder.step2_fill_inputs(miner_account.get_keys(), sources); - txin_to_key& in_to_key = boost::get(builder.m_tx.vin.front()); - in_to_key.key_offsets.front() = std::numeric_limits::max(); + TransactionInputToKey& in_to_key = boost::get(builder.m_tx.vin.front()); + in_to_key.keyOffsets.front() = std::numeric_limits::max(); builder.step3_fill_outputs(destinations); builder.step4_calc_hash(); builder.step5_sign(sources); @@ -448,13 +414,13 @@ bool gen_tx_mixed_key_offest_not_exist::generate(std::vector& REWIND_BLOCKS(events, blk_1r, blk_1, miner_account); MAKE_ACCOUNT(events, alice_account); MAKE_ACCOUNT(events, bob_account); - MAKE_TX_LIST_START(events, txs_0, miner_account, bob_account, MK_COINS(1) + TESTS_DEFAULT_FEE, blk_1); - MAKE_TX_LIST(events, txs_0, miner_account, alice_account, MK_COINS(1) + TESTS_DEFAULT_FEE, blk_1); + MAKE_TX_LIST_START(events, txs_0, miner_account, bob_account, MK_COINS(1) + m_currency.minimumFee(), blk_1); + MAKE_TX_LIST(events, txs_0, miner_account, alice_account, MK_COINS(1) + m_currency.minimumFee(), blk_1); MAKE_NEXT_BLOCK_TX_LIST(events, blk_2, blk_1r, miner_account, txs_0); std::vector sources; std::vector destinations; - fill_tx_sources_and_destinations(events, blk_2, bob_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 1, sources, destinations); + fill_tx_sources_and_destinations(events, blk_2, bob_account, miner_account, MK_COINS(1), m_currency.minimumFee(), 1, sources, destinations); sources.front().outputs[(sources.front().real_output + 1) % 2].first = std::numeric_limits::max(); @@ -481,17 +447,17 @@ bool gen_tx_key_image_not_derive_from_tx_key::generate(std::vector sources; std::vector destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), m_currency.minimumFee(), 0, sources, destinations); tx_builder builder; builder.step1_init(); builder.step2_fill_inputs(miner_account.get_keys(), sources); - txin_to_key& in_to_key = boost::get(builder.m_tx.vin.front()); - keypair kp = keypair::generate(); + TransactionInputToKey& in_to_key = boost::get(builder.m_tx.vin.front()); + KeyPair kp = KeyPair::generate(); key_image another_ki; crypto::generate_key_image(kp.pub, kp.sec, another_ki); - in_to_key.k_image = another_ki; + in_to_key.keyImage = another_ki; builder.step3_fill_outputs(destinations); builder.step4_calc_hash(); @@ -517,15 +483,15 @@ bool gen_tx_key_image_is_invalid::generate(std::vector& events std::vector sources; std::vector destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), m_currency.minimumFee(), 0, sources, destinations); tx_builder builder; builder.step1_init(); builder.step2_fill_inputs(miner_account.get_keys(), sources); - txin_to_key& in_to_key = boost::get(builder.m_tx.vin.front()); + TransactionInputToKey& in_to_key = boost::get(builder.m_tx.vin.front()); crypto::public_key pub = generate_invalid_pub_key(); - memcpy(&in_to_key.k_image, &pub, sizeof(crypto::ec_point)); + memcpy(&in_to_key.keyImage, &pub, sizeof(crypto::ec_point)); builder.step3_fill_outputs(destinations); builder.step4_calc_hash(); @@ -559,11 +525,11 @@ bool gen_tx_check_input_unlock_time::generate(std::vector& eve accounts[i] = acc; } - std::list txs_0; + std::list txs_0; auto make_tx_to_acc = [&](size_t acc_idx, uint64_t unlock_time) { txs_0.push_back(make_simple_tx_with_unlock_time(events, blk_1, miner_account, accounts[acc_idx], - MK_COINS(1) + TESTS_DEFAULT_FEE, unlock_time)); + MK_COINS(1) + m_currency.minimumFee(), m_currency.minimumFee(), unlock_time)); events.push_back(txs_0.back()); }; @@ -576,10 +542,11 @@ bool gen_tx_check_input_unlock_time::generate(std::vector& eve make_tx_to_acc(5, time(0) + 60 * 60); MAKE_NEXT_BLOCK_TX_LIST(events, blk_2, blk_1r, miner_account, txs_0); - std::list txs_1; + std::list txs_1; auto make_tx_from_acc = [&](size_t acc_idx, bool invalid) { - transaction tx = make_simple_tx_with_unlock_time(events, blk_2, accounts[acc_idx], miner_account, MK_COINS(1), 0); + Transaction tx = make_simple_tx_with_unlock_time(events, blk_2, accounts[acc_idx], miner_account, MK_COINS(1), + m_currency.minimumFee(), 0); if (invalid) { DO_CALLBACK(events, "mark_invalid_tx"); @@ -612,14 +579,14 @@ bool gen_tx_txout_to_key_has_invalid_key::generate(std::vector std::vector sources; std::vector destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), m_currency.minimumFee(), 0, sources, destinations); tx_builder builder; builder.step1_init(); builder.step2_fill_inputs(miner_account.get_keys(), sources); builder.step3_fill_outputs(destinations); - txout_to_key& out_to_key = boost::get(builder.m_tx.vout.front().target); + TransactionOutputToKey& out_to_key = boost::get(builder.m_tx.vout.front().target); out_to_key.key = generate_invalid_pub_key(); builder.step4_calc_hash(); @@ -641,7 +608,7 @@ bool gen_tx_output_with_zero_amount::generate(std::vector& eve std::vector sources; std::vector destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), m_currency.minimumFee(), 0, sources, destinations); tx_builder builder; builder.step1_init(); @@ -659,48 +626,6 @@ bool gen_tx_output_with_zero_amount::generate(std::vector& eve return true; } -bool gen_tx_output_is_not_txout_to_key::generate(std::vector& events) const -{ - uint64_t ts_start = 1338224400; - - GENERATE_ACCOUNT(miner_account); - MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); - REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); - - std::vector sources; - std::vector destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); - - tx_builder builder; - builder.step1_init(); - builder.step2_fill_inputs(miner_account.get_keys(), sources); - - builder.m_tx.vout.push_back(tx_out()); - builder.m_tx.vout.back().amount = 1; - builder.m_tx.vout.back().target = txout_to_script(); - - builder.step4_calc_hash(); - builder.step5_sign(sources); - - DO_CALLBACK(events, "mark_invalid_tx"); - events.push_back(builder.m_tx); - - builder.step1_init(); - builder.step2_fill_inputs(miner_account.get_keys(), sources); - - builder.m_tx.vout.push_back(tx_out()); - builder.m_tx.vout.back().amount = 1; - builder.m_tx.vout.back().target = txout_to_scripthash(); - - builder.step4_calc_hash(); - builder.step5_sign(sources); - - DO_CALLBACK(events, "mark_invalid_tx"); - events.push_back(builder.m_tx); - - return true; -} - bool gen_tx_signatures_are_invalid::generate(std::vector& events) const { uint64_t ts_start = 1338224400; @@ -711,8 +636,8 @@ bool gen_tx_signatures_are_invalid::generate(std::vector& even REWIND_BLOCKS(events, blk_1r, blk_1, miner_account); MAKE_ACCOUNT(events, alice_account); MAKE_ACCOUNT(events, bob_account); - MAKE_TX_LIST_START(events, txs_0, miner_account, bob_account, MK_COINS(1) + TESTS_DEFAULT_FEE, blk_1); - MAKE_TX_LIST(events, txs_0, miner_account, alice_account, MK_COINS(1) + TESTS_DEFAULT_FEE, blk_1); + MAKE_TX_LIST_START(events, txs_0, miner_account, bob_account, MK_COINS(1) + m_currency.minimumFee(), blk_1); + MAKE_TX_LIST(events, txs_0, miner_account, alice_account, MK_COINS(1) + m_currency.minimumFee(), blk_1); MAKE_NEXT_BLOCK_TX_LIST(events, blk_2, blk_1r, miner_account, txs_0); MAKE_TX(events, tx_0, miner_account, miner_account, MK_COINS(60), blk_2); @@ -723,7 +648,7 @@ bool gen_tx_signatures_are_invalid::generate(std::vector& even // Tx with nmix = 0 without signatures DO_CALLBACK(events, "mark_invalid_tx"); - blobdata sr_tx = t_serializable_object_to_blob(static_cast(tx_0)); + blobdata sr_tx = t_serializable_object_to_blob(static_cast(tx_0)); events.push_back(serialized_transaction(sr_tx)); // Tx with nmix = 0 have a few inputs, and not enough signatures @@ -740,7 +665,7 @@ bool gen_tx_signatures_are_invalid::generate(std::vector& even // Tx with nmix = 1 without signatures DO_CALLBACK(events, "mark_invalid_tx"); - sr_tx = t_serializable_object_to_blob(static_cast(tx_1)); + sr_tx = t_serializable_object_to_blob(static_cast(tx_1)); events.push_back(serialized_transaction(sr_tx)); // Tx with nmix = 1 have not enough signatures @@ -757,3 +682,205 @@ bool gen_tx_signatures_are_invalid::generate(std::vector& even return true; } + +MultiSigTx_OutputSignatures::MultiSigTx_OutputSignatures(size_t givenKeys, uint32_t requiredSignatures, bool shouldSucceed) : + m_givenKeys(givenKeys), m_requiredSignatures(requiredSignatures), m_shouldSucceed(shouldSucceed) { + + for (size_t i = 0; i < m_givenKeys; ++i) { + account_base acc; + acc.generate(); + m_outputAccounts.push_back(acc); + } +} + + +bool MultiSigTx_OutputSignatures::generate(std::vector& events) const { + TestGenerator generator(m_currency, events); + return generate(generator); +} + +bool MultiSigTx_OutputSignatures::generate(TestGenerator& generator) const { + + generator.generateBlocks(m_currency.minedMoneyUnlockWindow()); + + std::vector sources; + std::vector destinations; + fill_tx_sources_and_destinations(generator.events, generator.lastBlock, generator.minerAccount, generator.minerAccount, + MK_COINS(1), m_currency.minimumFee(), 0, sources, destinations); + + tx_builder builder; + builder.step1_init(); + builder.step2_fill_inputs(generator.minerAccount.get_keys(), sources); + + TransactionOutputMultisignature target; + + for (const auto& acc : m_outputAccounts) { + target.keys.push_back(acc.get_keys().m_account_address.m_spendPublicKey); + } + target.requiredSignatures = m_requiredSignatures; + TransactionOutput txOut = { MK_COINS(1), target }; + builder.m_tx.vout.push_back(txOut); + + builder.step4_calc_hash(); + builder.step5_sign(sources); + + if (!m_shouldSucceed) { + generator.addCallback("mark_invalid_tx"); + } + + generator.addEvent(builder.m_tx); + + if (!m_shouldSucceed) { + generator.addCallback("mark_invalid_block"); + } + + generator.makeNextBlock(builder.m_tx); + + return true; + +} + +bool MultiSigTx_InvalidOutputSignature::generate(std::vector& events) const { + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); + + std::vector sources; + std::vector destinations; + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), m_currency.minimumFee(), 0, sources, destinations); + + tx_builder builder; + builder.step1_init(); + builder.step2_fill_inputs(miner_account.get_keys(), sources); + + TransactionOutputMultisignature target; + + crypto::public_key pk; + crypto::secret_key sk; + crypto::generate_keys(pk, sk); + + // fill with 1 valid key + target.keys.push_back(pk); + // and 1 invalid + target.keys.push_back(generate_invalid_pub_key()); + + target.requiredSignatures = 2; + + TransactionOutput txOut = { MK_COINS(1), target }; + builder.m_tx.vout.push_back(txOut); + + builder.step4_calc_hash(); + builder.step5_sign(sources); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(builder.m_tx); + + return true; +} + +namespace +{ + void fillMultisignatureInput(TestGenerator& generator, tx_builder& builder, uint64_t inputAmount, uint32_t givenSignatures) { + + builder.step1_init(); + + // create input + TransactionInputMultisignature input; + input.amount = inputAmount; + input.signatures = givenSignatures; + input.outputIndex = 0; + builder.m_tx.vin.push_back(input); + + // create output + std::vector destinations; + destinations.emplace_back(inputAmount - generator.currency().minimumFee(), generator.minerAccount.get_keys().m_account_address); + builder.step3_fill_outputs(destinations); + + // calc hash + builder.step4_calc_hash(); + + } +} + + +MultiSigTx_Input::MultiSigTx_Input( + size_t givenKeys, uint32_t requiredSignatures, uint32_t givenSignatures, bool inputShouldSucceed) : + MultiSigTx_OutputSignatures(givenKeys, requiredSignatures, true), + m_givenSignatures(givenSignatures), + m_inputShouldSucceed(inputShouldSucceed) {} + +bool MultiSigTx_Input::generate(std::vector& events) const { + + TestGenerator generator(m_currency, events); + + // create outputs + MultiSigTx_OutputSignatures::generate(generator); + + tx_builder builder; + fillMultisignatureInput(generator, builder, MK_COINS(1), m_givenSignatures); + + // calc signatures + builder.m_tx.signatures.resize(builder.m_tx.signatures.size() + 1); + auto& outsigs = builder.m_tx.signatures.back(); + + for (size_t i = 0; i < m_givenSignatures; ++i) { + const auto& pk = m_outputAccounts[i].get_keys().m_account_address.m_spendPublicKey; + const auto& sk = m_outputAccounts[i].get_keys().m_spend_secret_key; + + crypto::signature sig; + crypto::generate_signature(builder.m_tx_prefix_hash, pk, sk, sig); + outsigs.push_back(sig); + } + + if (!m_inputShouldSucceed) { + generator.addCallback("mark_invalid_tx"); + } + + generator.addEvent(builder.m_tx); + return true; +} + + +MultiSigTx_BadInputSignature::MultiSigTx_BadInputSignature() : + MultiSigTx_OutputSignatures(1, 1, true) { +} + + +bool MultiSigTx_BadInputSignature::generate(std::vector& events) const { + + TestGenerator generator(m_currency, events); + + // create outputs + MultiSigTx_OutputSignatures::generate(generator); + + tx_builder builder; + fillMultisignatureInput(generator, builder, MK_COINS(1), 1); + + // calc signatures + builder.m_tx.signatures.resize(builder.m_tx.signatures.size() + 1); + auto& outsigs = builder.m_tx.signatures.back(); + + const auto& pk = m_outputAccounts[0].get_keys().m_account_address.m_spendPublicKey; + const auto& sk = m_outputAccounts[0].get_keys().m_spend_secret_key; + + // modify the transaction prefix hash + crypto::hash badHash = builder.m_tx_prefix_hash; + *reinterpret_cast(&badHash) = 0xdead; + + // sign the hash + crypto::signature sig; + crypto::generate_signature(badHash, pk, sk, sig); + outsigs.push_back(sig); + + // transaction with bad signature should be rejected + generator.addCallback("mark_invalid_tx"); + generator.addEvent(builder.m_tx); + + // blocks with transaction with bad signature should be rejected + generator.addCallback("mark_invalid_block"); + generator.makeNextBlock(builder.m_tx); + + return true; +} diff --git a/tests/core_tests/tx_validation.h b/tests/core_tests/tx_validation.h index 4ddd7c4d..50b29aba 100644 --- a/tests/core_tests/tx_validation.h +++ b/tests/core_tests/tx_validation.h @@ -28,7 +28,7 @@ struct get_tx_validation_base : public test_chain_unit_base REGISTER_CALLBACK_METHOD(get_tx_validation_base, mark_invalid_block); } - bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& /*tx*/) + bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::Transaction& /*tx*/) { if (m_invalid_tx_index == event_idx) return tvc.m_verifivation_failed; @@ -36,7 +36,7 @@ struct get_tx_validation_base : public test_chain_unit_base return !tvc.m_verifivation_failed && tx_added; } - bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*block*/) + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::Block& /*block*/) { if (m_invalid_block_index == event_idx) return bvc.m_verifivation_failed; @@ -71,11 +71,6 @@ struct gen_tx_unlock_time : public get_tx_validation_base bool generate(std::vector& events) const; }; -struct gen_tx_input_is_not_txin_to_key : public get_tx_validation_base -{ - bool generate(std::vector& events) const; -}; - struct gen_tx_no_inputs_no_outputs : public get_tx_validation_base { bool generate(std::vector& events) const; @@ -96,7 +91,7 @@ struct gen_tx_invalid_input_amount : public get_tx_validation_base bool generate(std::vector& events) const; }; -struct gen_tx_input_wo_key_offsets : public get_tx_validation_base +struct gen_tx_in_to_key_wo_key_offsets : public get_tx_validation_base { bool generate(std::vector& events) const; }; @@ -141,12 +136,42 @@ struct gen_tx_output_with_zero_amount : public get_tx_validation_base bool generate(std::vector& events) const; }; -struct gen_tx_output_is_not_txout_to_key : public get_tx_validation_base -{ - bool generate(std::vector& events) const; -}; - struct gen_tx_signatures_are_invalid : public get_tx_validation_base { bool generate(std::vector& events) const; }; + +// MultiSignature + +class TestGenerator; + +struct MultiSigTx_OutputSignatures : public get_tx_validation_base { + MultiSigTx_OutputSignatures(size_t givenKeys, uint32_t requiredSignatures, bool shouldSucceed); + + bool generate(std::vector& events) const; + bool generate(TestGenerator& generator) const; + + const size_t m_givenKeys; + const uint32_t m_requiredSignatures; + const bool m_shouldSucceed; + std::vector m_outputAccounts; +}; + +struct MultiSigTx_InvalidOutputSignature : public get_tx_validation_base { + bool generate(std::vector& events) const; +}; + + +struct MultiSigTx_Input : public MultiSigTx_OutputSignatures { + MultiSigTx_Input(size_t givenKeys, uint32_t requiredSignatures, uint32_t givenSignatures, bool shouldSucceed); + bool generate(std::vector& events) const; + + const bool m_inputShouldSucceed; + const uint32_t m_givenSignatures; +}; + + +struct MultiSigTx_BadInputSignature : public MultiSigTx_OutputSignatures { + MultiSigTx_BadInputSignature(); + bool generate(std::vector& events) const; +}; diff --git a/tests/core_tests/upgrade.cpp b/tests/core_tests/upgrade.cpp new file mode 100644 index 00000000..3ccb33d1 --- /dev/null +++ b/tests/core_tests/upgrade.cpp @@ -0,0 +1,251 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "upgrade.h" + +using namespace epee; +using namespace cryptonote; + +namespace { + bool makeBlocks(std::vector& events, test_generator& generator, Block& lastBlock, + const Block& parentBlock, const cryptonote::account_base& minerAcc, size_t count, + uint8_t majorVersion, uint8_t minorVersion) { + cryptonote::Block prevBlock = parentBlock; + for (size_t i = 0; i < count; ++i) { + cryptonote::Block b; + bool r = generator.constructBlockManually(b, prevBlock, minerAcc, test_generator::bf_major_ver | test_generator::bf_minor_ver, + majorVersion, minorVersion); + if (!r) { + return false; + } else { + prevBlock = b; + events.push_back(b); + } + } + + lastBlock = prevBlock; + + return true; + } +} + +gen_upgrade::gen_upgrade() : m_invalidBlockIndex(0), m_checkBlockTemplateVersionCallCounter(0), + m_coinsInCirculationBeforeUpgrade(0), m_coinsInCirculationAfterUpgrade(0) { + REGISTER_CALLBACK_METHOD(gen_upgrade, markInvalidBlock); + REGISTER_CALLBACK_METHOD(gen_upgrade, checkBlockTemplateVersionIsV1); + REGISTER_CALLBACK_METHOD(gen_upgrade, checkBlockTemplateVersionIsV2); + REGISTER_CALLBACK_METHOD(gen_upgrade, checkBlockRewardEqFee); + REGISTER_CALLBACK_METHOD(gen_upgrade, checkBlockRewardIsZero); + REGISTER_CALLBACK_METHOD(gen_upgrade, rememberCoinsInCirculationBeforeUpgrade); + REGISTER_CALLBACK_METHOD(gen_upgrade, rememberCoinsInCirculationAfterUpgrade); +} + +bool gen_upgrade::generate(std::vector& events) const { + const uint64_t tsStart = 1338224400; + + GENERATE_ACCOUNT(minerAccount); + MAKE_GENESIS_BLOCK(events, blk0, minerAccount, tsStart); + + // Vote for upgrade + Block blk1; + if (!makeBlocks(events, generator, blk1, blk0, minerAccount, m_currency.minNumberVotingBlocks(), BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_1)) { + return false; + } + + if (!checkBeforeUpgrade(events, generator, blk1, minerAccount, true)) { + return false; + } + + // Fill m_currency.upgradeVotingWindow() + Block blk2; + if (!makeBlocks(events, generator, blk2, blk1, minerAccount, m_currency.upgradeVotingWindow() - m_currency.minNumberVotingBlocks() - 1, + BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_0)) { + return false; + } + + // Upgrade voting complete! + uint64_t votingCompleteHeight = get_block_height(blk2); + uint64_t upgradeHeight = m_currency.calculateUpgradeHeight(votingCompleteHeight); + + if (!checkBeforeUpgrade(events, generator, blk2, minerAccount, true)) { + return false; + } + + // Create blocks up to upgradeHeight + Block blk3; + if (!makeBlocks(events, generator, blk3, blk2, minerAccount, upgradeHeight - votingCompleteHeight - 1, BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_0)) { + return false; + } + + if (!checkBeforeUpgrade(events, generator, blk3, minerAccount, false)) { + return false; + } + + // Create last block with version 1.x + Block blk4; + if (!makeBlocks(events, generator, blk4, blk3, minerAccount, 1, BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_0)) { + return false; + } + + generator.defaultMajorVersion = BLOCK_MAJOR_VERSION_2; + generator.defaultMinorVersion = BLOCK_MINOR_VERSION_0; + + if (!checkAfterUpgrade(events, generator, blk4, minerAccount)) { + return false; + } + + // Create a few blocks with version 2.0 + Block blk5; + if (!makeBlocks(events, generator, blk5, blk4, minerAccount, 3, BLOCK_MAJOR_VERSION_2, BLOCK_MINOR_VERSION_0)) { + return false; + } + + if (!checkAfterUpgrade(events, generator, blk5, minerAccount)) { + return false; + } + + return true; +} + +bool gen_upgrade::checkBeforeUpgrade(std::vector& events, test_generator& generator, + const cryptonote::Block& parentBlock, const cryptonote::account_base& minerAcc, + bool checkReward) const { + // Checking 1: get_block_templare returns block with major version 1 + DO_CALLBACK(events, "checkBlockTemplateVersionIsV1"); + + // Checking 2: penalty doesn't apply to transactions fee + if (checkReward) { + // Add block to the blockchain, later it become an alternative + DO_CALLBACK(events, "rememberCoinsInCirculationBeforeUpgrade"); + MAKE_TX_LIST_START(events, txs, minerAcc, minerAcc, MK_COINS(1), parentBlock); + Block alternativeBlk; + if (!generator.constructMaxSizeBlock(alternativeBlk, parentBlock, minerAcc, m_currency.rewardBlocksWindow(), txs)) { + return false; + } + events.push_back(alternativeBlk); + DO_CALLBACK(events, "checkBlockRewardEqFee"); + } + + // Checking 3: block with version 2.0 doesn't accepted + Block badBlock; + DO_CALLBACK(events, "markInvalidBlock"); + return makeBlocks(events, generator, badBlock, parentBlock, minerAcc, 1, BLOCK_MAJOR_VERSION_2, BLOCK_MINOR_VERSION_0); +} + +bool gen_upgrade::checkAfterUpgrade(std::vector& events, test_generator& generator, + const cryptonote::Block& parentBlock, const cryptonote::account_base& minerAcc) const { + // Checking 1: get_block_templare returns block with major version 2 + DO_CALLBACK(events, "checkBlockTemplateVersionIsV2"); + + // Checking 2: penalty applies to transactions fee + // Add block to the blockchain, later it become an alternative + DO_CALLBACK(events, "rememberCoinsInCirculationAfterUpgrade"); + MAKE_TX_LIST_START(events, txs, minerAcc, minerAcc, MK_COINS(1), parentBlock); + Block alternativeBlk; + if (!generator.constructMaxSizeBlock(alternativeBlk, parentBlock, minerAcc, m_currency.rewardBlocksWindow(), txs)) { + return false; + } + events.push_back(alternativeBlk); + DO_CALLBACK(events, "checkBlockRewardIsZero"); + + // Checking 3: block with version 1.0 doesn't accepted + Block badBlock; + DO_CALLBACK(events, "markInvalidBlock"); + if (!makeBlocks(events, generator, badBlock, parentBlock, minerAcc, 1, BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_0)) { + return false; + } + + // Checking 2: block with version 1.1 doesn't accepted + DO_CALLBACK(events, "markInvalidBlock"); + return makeBlocks(events, generator, badBlock, parentBlock, minerAcc, 1, BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_1); +} + +bool gen_upgrade::check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t eventIdx, const cryptonote::Block& /*blk*/) { + if (m_invalidBlockIndex == eventIdx) { + m_invalidBlockIndex = 0; + return bvc.m_verifivation_failed; + } else { + return !bvc.m_verifivation_failed; + } +} + +bool gen_upgrade::markInvalidBlock(cryptonote::core& /*c*/, size_t evIndex, const std::vector& /*events*/) { + m_invalidBlockIndex = evIndex + 1; + return true; +} + +bool gen_upgrade::checkBlockTemplateVersionIsV1(cryptonote::core& c, size_t /*evIndex*/, const std::vector& /*events*/) { + DEFINE_TESTS_ERROR_CONTEXT("gen_upgrade::checkBlockTemplateVersionIsV1"); + CHECK_TEST_CONDITION(checkBlockTemplateVersion(c, BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_1)); + return true; +} + +bool gen_upgrade::checkBlockTemplateVersionIsV2(cryptonote::core& c, size_t /*evIndex*/, const std::vector& /*events*/) { + DEFINE_TESTS_ERROR_CONTEXT("gen_upgrade::checkBlockTemplateVersionIsV2"); + CHECK_TEST_CONDITION(checkBlockTemplateVersion(c, BLOCK_MAJOR_VERSION_2, BLOCK_MINOR_VERSION_0)); + return true; +} + +bool gen_upgrade::checkBlockTemplateVersion(cryptonote::core& c, uint8_t expectedMajorVersion, uint8_t expectedMinorVersion) { + DEFINE_TESTS_ERROR_CONTEXT("gen_upgrade::checkBlockTemplateVersion"); + + account_base account; + account.generate(); + + Block b; + difficulty_type diff; + uint64_t height; + CHECK_TEST_CONDITION(c.get_block_template(b, account.get_keys().m_account_address, diff, height, blobdata())); + CHECK_EQ(b.majorVersion, expectedMajorVersion); + CHECK_EQ(b.minorVersion, expectedMinorVersion); + + return true; +} + +bool gen_upgrade::checkBlockRewardEqFee(cryptonote::core& c, size_t evIndex, const std::vector& events) { + DEFINE_TESTS_ERROR_CONTEXT("gen_upgrade::checkBlockRewardEqFee"); + + Block blk = boost::get(events[evIndex - 1]); + uint64_t blockReward = get_outs_money_amount(blk.minerTx); + CHECK_EQ(blockReward, m_currency.minimumFee()); + + CHECK_EQ(m_coinsInCirculationBeforeUpgrade, c.get_blockchain_storage().getCoinsInCirculation()); + + return true; +} + +bool gen_upgrade::checkBlockRewardIsZero(cryptonote::core& c, size_t evIndex, const std::vector& events) { + DEFINE_TESTS_ERROR_CONTEXT("gen_upgrade::checkBlockRewardIsZero"); + + Block blk = boost::get(events[evIndex - 1]); + uint64_t blockReward = get_outs_money_amount(blk.minerTx); + CHECK_EQ(blockReward, 0); + + CHECK_EQ(m_coinsInCirculationAfterUpgrade - m_currency.minimumFee(), c.get_blockchain_storage().getCoinsInCirculation()); + + return true; +} + +bool gen_upgrade::rememberCoinsInCirculationBeforeUpgrade(cryptonote::core& c, size_t /*evIndex*/, const std::vector& /*events*/) { + m_coinsInCirculationBeforeUpgrade = c.get_blockchain_storage().getCoinsInCirculation(); + return true; +} + +bool gen_upgrade::rememberCoinsInCirculationAfterUpgrade(cryptonote::core& c, size_t /*evIndex*/, const std::vector& /*events*/) { + m_coinsInCirculationAfterUpgrade = c.get_blockchain_storage().getCoinsInCirculation(); + return true; +} diff --git a/tests/core_tests/upgrade.h b/tests/core_tests/upgrade.h new file mode 100644 index 00000000..1d5f5af2 --- /dev/null +++ b/tests/core_tests/upgrade.h @@ -0,0 +1,49 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once +#include "chaingen.h" + +struct gen_upgrade : public test_chain_unit_base +{ + gen_upgrade(); + + bool generate(std::vector& events) const; + + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t eventIdx, const cryptonote::Block& blk); + + bool markInvalidBlock(cryptonote::core& c, size_t evIndex, const std::vector& events); + bool checkBlockTemplateVersionIsV1(cryptonote::core& c, size_t evIndex, const std::vector& events); + bool checkBlockTemplateVersionIsV2(cryptonote::core& c, size_t evIndex, const std::vector& events); + bool checkBlockRewardEqFee(cryptonote::core& c, size_t evIndex, const std::vector& events); + bool checkBlockRewardIsZero(cryptonote::core& c, size_t evIndex, const std::vector& events); + bool rememberCoinsInCirculationBeforeUpgrade(cryptonote::core& c, size_t evIndex, const std::vector& events); + bool rememberCoinsInCirculationAfterUpgrade(cryptonote::core& c, size_t evIndex, const std::vector& events); + +private: + bool checkBeforeUpgrade(std::vector& events, test_generator& generator, + const cryptonote::Block& parentBlock, const cryptonote::account_base& minerAcc, bool checkReward) const; + bool checkAfterUpgrade(std::vector& events, test_generator& generator, + const cryptonote::Block& parentBlock, const cryptonote::account_base& minerAcc) const; + bool checkBlockTemplateVersion(cryptonote::core& c, uint8_t expectedMajorVersion, uint8_t expectedMinorVersion); + +private: + size_t m_invalidBlockIndex; + size_t m_checkBlockTemplateVersionCallCounter; + uint64_t m_coinsInCirculationBeforeUpgrade; + uint64_t m_coinsInCirculationAfterUpgrade; +}; diff --git a/tests/difficulty/difficulty.cpp b/tests/difficulty/difficulty.cpp index 1de712b1..dba4fc85 100644 --- a/tests/difficulty/difficulty.cpp +++ b/tests/difficulty/difficulty.cpp @@ -24,16 +24,21 @@ #include "cryptonote_config.h" #include "cryptonote_core/difficulty.h" +#include "cryptonote_core/Currency.h" using namespace std; -#define DEFAULT_TEST_DIFFICULTY_TARGET 120 - int main(int argc, char *argv[]) { if (argc != 2) { cerr << "Wrong arguments" << endl; return 1; } + cryptonote::CurrencyBuilder currencyBuilder; + currencyBuilder.difficultyTarget(120); + currencyBuilder.difficultyWindow(720); + currencyBuilder.difficultyCut(60); + currencyBuilder.difficultyLag(15); + cryptonote::Currency currency = currencyBuilder.currency(); vector timestamps, cumulative_difficulties; fstream data(argv[1], fstream::in); data.exceptions(fstream::badbit); @@ -42,16 +47,16 @@ int main(int argc, char *argv[]) { size_t n = 0; while (data >> timestamp >> difficulty) { size_t begin, end; - if (n < DIFFICULTY_WINDOW + DIFFICULTY_LAG) { + if (n < currency.difficultyWindow() + currency.difficultyLag()) { begin = 0; - end = min(n, (size_t) DIFFICULTY_WINDOW); + end = min(n, currency.difficultyWindow()); } else { - end = n - DIFFICULTY_LAG; - begin = end - DIFFICULTY_WINDOW; + end = n - currency.difficultyLag(); + begin = end - currency.difficultyWindow(); } - uint64_t res = cryptonote::next_difficulty( + uint64_t res = currency.nextDifficulty( vector(timestamps.begin() + begin, timestamps.begin() + end), - vector(cumulative_difficulties.begin() + begin, cumulative_difficulties.begin() + end), DEFAULT_TEST_DIFFICULTY_TARGET); + vector(cumulative_difficulties.begin() + begin, cumulative_difficulties.begin() + end)); if (res != difficulty) { cerr << "Wrong difficulty for block " << n << endl << "Expected: " << difficulty << endl diff --git a/tests/functional_tests/main.cpp b/tests/functional_tests/main.cpp index 5708d890..87eac61b 100644 --- a/tests/functional_tests/main.cpp +++ b/tests/functional_tests/main.cpp @@ -18,6 +18,7 @@ #include #include "include_base_utils.h" +#include "string_tools.h" using namespace epee; #include "common/command_line.h" diff --git a/tests/functional_tests/transactions_flow_test.cpp b/tests/functional_tests/transactions_flow_test.cpp index f46df568..b97c51d6 100644 --- a/tests/functional_tests/transactions_flow_test.cpp +++ b/tests/functional_tests/transactions_flow_test.cpp @@ -16,11 +16,14 @@ // along with Bytecoin. If not, see . #include +#include #include #include #include "include_base_utils.h" using namespace epee; + +#include "cryptonote_core/Currency.h" #include "wallet/wallet2.h" using namespace cryptonote; @@ -38,7 +41,7 @@ inline uint64_t random(const uint64_t max_value) { (uint64_t(rand())<<48)) % max_value; } -bool do_send_money(tools::wallet2& w1, tools::wallet2& w2, size_t mix_in_factor, uint64_t amount_to_transfer, transaction& tx, size_t parts=1) +bool do_send_money(tools::wallet2& w1, tools::wallet2& w2, size_t mix_in_factor, uint64_t amount_to_transfer, Transaction& tx, size_t parts=1) { CHECK_AND_ASSERT_MES(parts > 0, false, "parts must be > 0"); @@ -65,7 +68,8 @@ bool do_send_money(tools::wallet2& w1, tools::wallet2& w2, size_t mix_in_factor, try { - w1.transfer(dsts, mix_in_factor, 0, MINIMUM_FEE, std::vector(), tools::detail::null_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); + w1.transfer(dsts, mix_in_factor, 0, w1.currency().minimumFee(), std::vector(), + tools::detail::null_split_strategy, tools::tx_dust_policy(w1.currency().defaultDustThreshold()), tx); return true; } catch (const std::exception&) @@ -97,7 +101,8 @@ bool transactions_flow_test(std::string& working_folder, uint64_t amount_to_transfer, size_t mix_in_factor, size_t transactions_count, size_t transactions_per_second) { LOG_PRINT_L0("-----------------------STARTING TRANSACTIONS FLOW TEST-----------------------"); - tools::wallet2 w1, w2; + cryptonote::Currency currency = cryptonote::CurrencyBuilder().currency(); + tools::wallet2 w1(currency), w2(currency); if(path_source_wallet.empty()) path_source_wallet = generate_random_wallet_name(); @@ -130,8 +135,8 @@ bool transactions_flow_test(std::string& working_folder, w2.init(daemon_addr_b); LOG_PRINT_GREEN("Using wallets: " << ENDL - << "Source: " << w1.get_account().get_public_address_str() << ENDL << "Path: " << working_folder + "/" + path_source_wallet << ENDL - << "Target: " << w2.get_account().get_public_address_str() << ENDL << "Path: " << working_folder + "/" + path_terget_wallet, LOG_LEVEL_1); + << "Source: " << currency.accountAddressAsString(w1.get_account()) << ENDL << "Path: " << working_folder + "/" + path_source_wallet << ENDL + << "Target: " << currency.accountAddressAsString(w2.get_account()) << ENDL << "Path: " << working_folder + "/" + path_terget_wallet, LOG_LEVEL_1); //lets do some money epee::net_utils::http::http_simple_client http_client; @@ -142,7 +147,7 @@ bool transactions_flow_test(std::string& working_folder, COMMAND_RPC_START_MINING::request daemon_req = AUTO_VAL_INIT(daemon_req); COMMAND_RPC_START_MINING::response daemon_rsp = AUTO_VAL_INIT(daemon_rsp); - daemon_req.miner_address = w1.get_account().get_public_address_str(); + daemon_req.miner_address = currency.accountAddressAsString(w1.get_account()); daemon_req.threads_count = 9; r = net_utils::invoke_http_json_remote_command2(daemon_addr_a + "/start_mining", daemon_req, daemon_rsp, http_client, 10000); CHECK_AND_ASSERT_MES(r, false, "failed to get getrandom_outs"); @@ -169,8 +174,8 @@ bool transactions_flow_test(std::string& working_folder, size_t count = 0; BOOST_FOREACH(tools::wallet2::transfer_details& td, incoming_transfers) { - cryptonote::transaction tx_s; - bool r = do_send_money(w1, w1, 0, td.m_tx.vout[td.m_internal_output_index].amount - MINIMUM_FEE, tx_s, 50); + cryptonote::Transaction tx_s; + bool r = do_send_money(w1, w1, 0, td.m_tx.vout[td.m_internal_output_index].amount - currency.minimumFee(), tx_s, 50); CHECK_AND_ASSERT_MES(r, false, "Failed to send starter tx " << get_transaction_hash(tx_s)); LOG_PRINT_GREEN("Starter transaction sent " << get_transaction_hash(tx_s), LOG_LEVEL_0); if(++count >= FIRST_N_TRANSFERS) @@ -189,7 +194,7 @@ bool transactions_flow_test(std::string& working_folder, size_t i = 0; struct tx_test_entry { - transaction tx; + Transaction tx; size_t m_received_count; uint64_t amount_transfered; }; @@ -198,14 +203,14 @@ bool transactions_flow_test(std::string& working_folder, for(i = 0; i != transactions_count; i++) { uint64_t amount_to_tx = (amount_to_transfer - transfered_money) > transfer_size ? transfer_size: (amount_to_transfer - transfered_money); - while(w1.unlocked_balance() < amount_to_tx + MINIMUM_FEE) + while(w1.unlocked_balance() < amount_to_tx + currency.minimumFee()) { misc_utils::sleep_no_w(1000); LOG_PRINT_L0("not enough money, waiting for cashback or mining"); w1.refresh(blocks_fetched, received_money, ok); } - transaction tx; + Transaction tx; /*size_t n_attempts = 0; while (!do_send_money(w1, w2, mix_in_factor, amount_to_tx, tx)) { n_attempts++; @@ -225,7 +230,7 @@ bool transactions_flow_test(std::string& working_folder, return false; } } - lst_sent_ki = boost::get(tx.vin[0]).k_image; + lst_sent_ki = boost::get(tx.vin[0]).keyImage; transfered_money += amount_to_tx; @@ -239,19 +244,21 @@ bool transactions_flow_test(std::string& working_folder, LOG_PRINT_L0( "waiting some new blocks..."); - misc_utils::sleep_no_w(DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN*20*1000);//wait two blocks before sync on another wallet on another daemon + //wait two blocks before sync on another wallet on another daemon + misc_utils::sleep_no_w(static_cast(currency.difficultyTarget() * 20 * 1000)); LOG_PRINT_L0( "refreshing..."); bool recvd_money = false; while(w2.refresh(blocks_fetched, recvd_money, ok) && ( (blocks_fetched && recvd_money) || !blocks_fetched ) ) { - misc_utils::sleep_no_w(DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN*1000);//wait two blocks before sync on another wallet on another daemon + //wait two blocks before sync on another wallet on another daemon + misc_utils::sleep_no_w(static_cast(currency.difficultyTarget() * 1000)); } uint64_t money_2 = w2.balance(); if(money_2 == transfered_money) { LOG_PRINT_GREEN("-----------------------FINISHING TRANSACTIONS FLOW TEST OK-----------------------", LOG_LEVEL_0); - LOG_PRINT_GREEN("transferred " << print_money(transfered_money) << " via " << i << " transactions" , LOG_LEVEL_0); + LOG_PRINT_GREEN("transferred " << currency.formatAmount(transfered_money) << " via " << i << " transactions" , LOG_LEVEL_0); return true; }else { @@ -274,7 +281,8 @@ bool transactions_flow_test(std::string& working_folder, } LOG_PRINT_RED_L0("-----------------------FINISHING TRANSACTIONS FLOW TEST FAILED-----------------------" ); - LOG_PRINT_RED_L0("income " << print_money(money_2) << " via " << i << " transactions, expected money = " << print_money(transfered_money) ); + LOG_PRINT_RED_L0("income " << currency.formatAmount(money_2) << " via " << i << + " transactions, expected money = " << currency.formatAmount(transfered_money) ); LOCAL_ASSERT(false); return false; } diff --git a/tests/functional_tests/transactions_generation_from_blockchain.cpp b/tests/functional_tests/transactions_generation_from_blockchain.cpp index d9ca1ed0..14f659f6 100644 --- a/tests/functional_tests/transactions_generation_from_blockchain.cpp +++ b/tests/functional_tests/transactions_generation_from_blockchain.cpp @@ -32,7 +32,7 @@ bool transactions_generation_from_blockchain(std::string& blockchain_folder_path CHECK_AND_ASSERT_MES(r, false, "failed to load blockchain"); //amount = 3000000000000 - //key_offsets = 1,2,3,4,5,10,12,27,31,33,34 + //keyOffsets = 1,2,3,4,5,10,12,27,31,33,34 // } @@ -40,7 +40,7 @@ tx_source_entry::output_entry make_outptu_entr_for_gindex(size_t i, std::map(vout[v[i].second].target).key; + oe.second = txs[v[i].first].boost::get(vout[v[i].second].target).key; return oe; } @@ -96,7 +96,7 @@ bool make_tx(blockchain_storage& bch) //size_t real_index = src.outputs.size() ? (rand() % src.outputs.size() ):0; tx_output_entry real_oe; real_oe.first = td.m_global_output_index; - real_oe.second = boost::get(td.m_tx.vout[td.m_internal_output_index].target).key; + real_oe.second = boost::get(td.m_tx.vout[td.m_internal_output_index].target).key; auto interted_it = src.outputs.insert(it_to_insert, real_oe); src.real_out_tx_key = td.m_tx.tx_pub_key; src.real_output = interted_it - src.outputs.begin(); diff --git a/tests/net_load_tests/clt.cpp b/tests/net_load_tests/clt.cpp index 9c503e18..c7a76da0 100644 --- a/tests/net_load_tests/clt.cpp +++ b/tests/net_load_tests/clt.cpp @@ -22,6 +22,8 @@ #include #include +#include + #include "gtest/gtest.h" #include "include_base_utils.h" diff --git a/tests/net_load_tests/net_load_tests.h b/tests/net_load_tests/net_load_tests.h index d7c56608..dcb00919 100644 --- a/tests/net_load_tests/net_load_tests.h +++ b/tests/net_load_tests/net_load_tests.h @@ -20,6 +20,7 @@ #include #include +#include #include "include_base_utils.h" #include "string_tools.h" diff --git a/tests/net_load_tests/srv.cpp b/tests/net_load_tests/srv.cpp index 9c884d2c..8fb13640 100644 --- a/tests/net_load_tests/srv.cpp +++ b/tests/net_load_tests/srv.cpp @@ -15,9 +15,12 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . +#include #include #include +#include + #include "include_base_utils.h" #include "misc_log_ex.h" #include "storages/levin_abstract_invoke2.h" diff --git a/tests/node_rpc_proxy_test/node_rpc_proxy_test.cpp b/tests/node_rpc_proxy_test/node_rpc_proxy_test.cpp index 11f84cb0..3ff889c3 100644 --- a/tests/node_rpc_proxy_test/node_rpc_proxy_test.cpp +++ b/tests/node_rpc_proxy_test/node_rpc_proxy_test.cpp @@ -119,7 +119,7 @@ int main(int argc, const char** argv) { LOG_PRINT_RED_L0("shutdown error"); } - cryptonote::transaction tx; + cryptonote::Transaction tx; nodeProxy.relayTransaction(tx, [](std::error_code ec) { if (!ec) { LOG_PRINT_L0("relayTransaction called successfully"); diff --git a/tests/performance_tests/check_ring_signature.h b/tests/performance_tests/check_ring_signature.h index 9cfedf2d..b5072647 100644 --- a/tests/performance_tests/check_ring_signature.h +++ b/tests/performance_tests/check_ring_signature.h @@ -59,12 +59,12 @@ public: bool test() { - const cryptonote::txin_to_key& txin = boost::get(m_tx.vin[0]); - return crypto::check_ring_signature(m_tx_prefix_hash, txin.k_image, this->m_public_key_ptrs, ring_size, m_tx.signatures[0].data()); + const cryptonote::TransactionInputToKey& txin = boost::get(m_tx.vin[0]); + return crypto::check_ring_signature(m_tx_prefix_hash, txin.keyImage, this->m_public_key_ptrs, ring_size, m_tx.signatures[0].data()); } private: cryptonote::account_base m_alice; - cryptonote::transaction m_tx; + cryptonote::Transaction m_tx; crypto::hash m_tx_prefix_hash; }; diff --git a/tests/performance_tests/construct_tx.h b/tests/performance_tests/construct_tx.h index ec91eeb0..ac4f1463 100644 --- a/tests/performance_tests/construct_tx.h +++ b/tests/performance_tests/construct_tx.h @@ -61,5 +61,5 @@ public: private: cryptonote::account_base m_alice; std::vector m_destinations; - cryptonote::transaction m_tx; + cryptonote::Transaction m_tx; }; diff --git a/tests/performance_tests/derive_public_key.h b/tests/performance_tests/derive_public_key.h index 937bc647..d456d4fa 100644 --- a/tests/performance_tests/derive_public_key.h +++ b/tests/performance_tests/derive_public_key.h @@ -33,14 +33,14 @@ public: return false; crypto::generate_key_derivation(m_tx_pub_key, m_bob.get_keys().m_view_secret_key, m_key_derivation); - m_spend_public_key = m_bob.get_keys().m_account_address.m_spend_public_key; + m_spend_public_key = m_bob.get_keys().m_account_address.m_spendPublicKey; return true; } bool test() { - cryptonote::keypair in_ephemeral; + cryptonote::KeyPair in_ephemeral; crypto::derive_public_key(m_key_derivation, 0, m_spend_public_key, in_ephemeral.pub); return true; } diff --git a/tests/performance_tests/derive_secret_key.h b/tests/performance_tests/derive_secret_key.h index 51226f09..cde62a0a 100644 --- a/tests/performance_tests/derive_secret_key.h +++ b/tests/performance_tests/derive_secret_key.h @@ -40,7 +40,7 @@ public: bool test() { - cryptonote::keypair in_ephemeral; + cryptonote::KeyPair in_ephemeral; crypto::derive_secret_key(m_key_derivation, 0, m_spend_secret_key, in_ephemeral.sec); return true; } diff --git a/tests/performance_tests/generate_key_image.h b/tests/performance_tests/generate_key_image.h index 32a8709e..29106b4b 100644 --- a/tests/performance_tests/generate_key_image.h +++ b/tests/performance_tests/generate_key_image.h @@ -39,7 +39,7 @@ public: crypto::key_derivation recv_derivation; crypto::generate_key_derivation(m_tx_pub_key, bob_keys.m_view_secret_key, recv_derivation); - crypto::derive_public_key(recv_derivation, 0, bob_keys.m_account_address.m_spend_public_key, m_in_ephemeral.pub); + crypto::derive_public_key(recv_derivation, 0, bob_keys.m_account_address.m_spendPublicKey, m_in_ephemeral.pub); crypto::derive_secret_key(recv_derivation, 0, bob_keys.m_spend_secret_key, m_in_ephemeral.sec); return true; @@ -53,5 +53,5 @@ public: } private: - cryptonote::keypair m_in_ephemeral; + cryptonote::KeyPair m_in_ephemeral; }; diff --git a/tests/performance_tests/generate_key_image_helper.h b/tests/performance_tests/generate_key_image_helper.h index c2ba87bd..121f20b0 100644 --- a/tests/performance_tests/generate_key_image_helper.h +++ b/tests/performance_tests/generate_key_image_helper.h @@ -17,7 +17,6 @@ #pragma once -#include "cryptonote_core/account.h" #include "cryptonote_core/cryptonote_basic.h" #include "cryptonote_core/cryptonote_format_utils.h" @@ -30,7 +29,7 @@ public: bool test() { - cryptonote::keypair in_ephemeral; + cryptonote::KeyPair in_ephemeral; crypto::key_image ki; return cryptonote::generate_key_image_helper(m_bob.get_keys(), m_tx_pub_key, 0, in_ephemeral, ki); } diff --git a/tests/performance_tests/is_out_to_acc.h b/tests/performance_tests/is_out_to_acc.h index f2031e11..8ea73941 100644 --- a/tests/performance_tests/is_out_to_acc.h +++ b/tests/performance_tests/is_out_to_acc.h @@ -17,7 +17,6 @@ #pragma once -#include "cryptonote_core/account.h" #include "cryptonote_core/cryptonote_basic.h" #include "cryptonote_core/cryptonote_format_utils.h" @@ -30,7 +29,7 @@ public: bool test() { - const cryptonote::txout_to_key& tx_out = boost::get(m_tx.vout[0].target); + const cryptonote::TransactionOutputToKey& tx_out = boost::get(m_tx.vout[0].target); return cryptonote::is_out_to_acc(m_bob.get_keys(), tx_out, m_tx_pub_key, 0); } }; diff --git a/tests/performance_tests/multi_tx_test_base.h b/tests/performance_tests/multi_tx_test_base.h index f6d81da9..ef3e912f 100644 --- a/tests/performance_tests/multi_tx_test_base.h +++ b/tests/performance_tests/multi_tx_test_base.h @@ -22,6 +22,7 @@ #include "cryptonote_core/account.h" #include "cryptonote_core/cryptonote_basic.h" #include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_core/Currency.h" #include "crypto/crypto.h" template @@ -37,15 +38,17 @@ public: { using namespace cryptonote; + Currency currency = CurrencyBuilder().currency(); + std::vector output_entries; for (size_t i = 0; i < ring_size; ++i) { m_miners[i].generate(); - if (!construct_miner_tx(0, 0, 0, 2, 0, m_miners[i].get_keys().m_account_address, m_miner_txs[i])) + if (!currency.constructMinerTx(0, 0, 0, 2, 0, m_miners[i].get_keys().m_account_address, m_miner_txs[i])) return false; - txout_to_key tx_out = boost::get(m_miner_txs[i].vout[0].target); + TransactionOutputToKey tx_out = boost::get(m_miner_txs[i].vout[0].target); output_entries.push_back(std::make_pair(i, tx_out.key)); m_public_keys[i] = tx_out.key; m_public_key_ptrs[i] = &m_public_keys[i]; @@ -67,7 +70,7 @@ public: protected: cryptonote::account_base m_miners[ring_size]; - cryptonote::transaction m_miner_txs[ring_size]; + cryptonote::Transaction m_miner_txs[ring_size]; uint64_t m_source_amount; std::vector m_sources; diff --git a/tests/performance_tests/single_tx_test_base.h b/tests/performance_tests/single_tx_test_base.h index bf499a01..7a670e0a 100644 --- a/tests/performance_tests/single_tx_test_base.h +++ b/tests/performance_tests/single_tx_test_base.h @@ -28,9 +28,10 @@ public: { using namespace cryptonote; + Currency currency = CurrencyBuilder().currency(); m_bob.generate(); - if (!construct_miner_tx(0, 0, 0, 2, 0, m_bob.get_keys().m_account_address, m_tx)) + if (!currency.constructMinerTx(0, 0, 0, 2, 0, m_bob.get_keys().m_account_address, m_tx)) return false; m_tx_pub_key = get_tx_pub_key_from_extra(m_tx); @@ -39,6 +40,6 @@ public: protected: cryptonote::account_base m_bob; - cryptonote::transaction m_tx; + cryptonote::Transaction m_tx; crypto::public_key m_tx_pub_key; }; diff --git a/tests/unit_tests/BlockingQueue.cpp b/tests/unit_tests/BlockingQueue.cpp new file mode 100644 index 00000000..df673387 --- /dev/null +++ b/tests/unit_tests/BlockingQueue.cpp @@ -0,0 +1,211 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include +#include "common/BlockingQueue.h" + +#include +#include +#include +#include + +class ParallelProcessor { +public: + + ParallelProcessor(size_t threads) + : m_threads(threads) {} + + template + void spawn(F f) { + for (auto& t : m_threads) { + t = std::thread(f); + } + } + + void join() { + for (auto& t : m_threads) { + t.join(); + } + } + +private: + + std::vector m_threads; + +}; + +// single producer, many consumers +void TestQueue_SPMC(unsigned iterations, unsigned threadCount, unsigned queueSize) { + + BlockingQueue bq(queueSize); + + ParallelProcessor processor(threadCount); + std::atomic result(0); + + processor.spawn([&bq, &result]{ + int v = 0; + int64_t sum = 0; + + while (bq.pop(v)) { + sum += v; + } + + result += sum; + // std::cout << "Sum: " << sum << std::endl; + }); + + int64_t expectedSum = 0; + + for (unsigned i = 0; i < iterations; ++i) { + expectedSum += i; + ASSERT_TRUE(bq.push(i)); + } + + bq.close(); + processor.join(); + + ASSERT_EQ(expectedSum, result.load()); +} + +void TestQueue_MPSC(unsigned iterations, unsigned threadCount, unsigned queueSize) { + + BlockingQueue bq(queueSize); + + ParallelProcessor processor(threadCount); + std::atomic counter(0); + std::atomic pushed(0); + + processor.spawn([&]{ + int v = 0; + int64_t sum = 0; + + for(;;) { + unsigned value = counter.fetch_add(1); + if (value >= iterations) + break; + + bq.push(value); + sum += value; + } + + pushed += sum; + // std::cout << "Sum: " << sum << std::endl; + }); + + int64_t expectedSum = 0; + + for (unsigned i = 0; i < iterations; ++i) { + int value; + ASSERT_TRUE(bq.pop(value)); + expectedSum += i; + } + + ASSERT_EQ(0, bq.size()); + + processor.join(); + + ASSERT_EQ(expectedSum, pushed); +} + + +TEST(BlockingQueue, SPMC) +{ + TestQueue_SPMC(10000, 1, 1); + TestQueue_SPMC(10000, 4, 1); + TestQueue_SPMC(10000, 16, 16); + TestQueue_SPMC(10000, 16, 100); +} + +TEST(BlockingQueue, MPSC) +{ + TestQueue_MPSC(10000, 1, 1); + TestQueue_MPSC(10000, 4, 1); + TestQueue_MPSC(10000, 16, 16); + TestQueue_MPSC(10000, 16, 100); +} + + +TEST(BlockingQueue, PerfTest) +{ + // TestQueue_SPMC(1000000, 32, 1); +} + +TEST(BlockingQueue, Close) +{ + BlockingQueue bq(4); + ParallelProcessor p(4); + + p.spawn([&bq] { + int v; + while (bq.pop(v)) + ; + }); + + bq.push(10); // enqueue 1 item + + bq.close(); // all threads should unblock and finish + p.join(); +} + +TEST(BlockingQueue, CloseAndWait) +{ + size_t queueSize = 100; + BlockingQueue bq(queueSize); + ParallelProcessor p(4); + + std::atomic itemsPopped(0); + + // fill the queue + for (int i = 0; i < queueSize; ++i) + bq.push(i); + + p.spawn([&bq, &itemsPopped] { + int v; + while (bq.pop(v)) { + itemsPopped += 1; + // some delay to make close() really wait + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + }); + + + // check with multiple closing + auto f1 = std::async(std::launch::async, [&] { bq.close(true); }); + auto f2 = std::async(std::launch::async, [&] { bq.close(true); }); + + bq.close(true); + + f1.get(); + f2.get(); + + ASSERT_EQ(queueSize, itemsPopped.load()); + + p.join(); +} + +TEST(BlockingQueue, AllowsMoveOnly) +{ + BlockingQueue> bq(1); + + std::unique_ptr v(new int(100)); + ASSERT_TRUE(bq.push(std::move(v))); + + std::unique_ptr popval; + bq.pop(popval); + + ASSERT_EQ(*popval, 100); +} diff --git a/tests/unit_tests/INodeStubs.cpp b/tests/unit_tests/INodeStubs.cpp index 6f0c8dc7..6f83aeb1 100644 --- a/tests/unit_tests/INodeStubs.cpp +++ b/tests/unit_tests/INodeStubs.cpp @@ -43,9 +43,9 @@ void INodeTrivialRefreshStub::doGetNewBlocks(std::list knownBlockI cryptonote::block_complete_entry e; e.block = cryptonote::t_serializable_object_to_blob(blockchain[m_lastHeight]); - for (auto hash: blockchain[m_lastHeight].tx_hashes) + for (auto hash : blockchain[m_lastHeight].txHashes) { - cryptonote::transaction tx; + cryptonote::Transaction tx; if (!m_blockchainGenerator.getTransactionByHash(hash, tx)) continue; @@ -72,13 +72,13 @@ void INodeTrivialRefreshStub::doGetTransactionOutsGlobalIndices(const crypto::ha callback(std::error_code()); } -void INodeTrivialRefreshStub::relayTransaction(const cryptonote::transaction& transaction, const Callback& callback) +void INodeTrivialRefreshStub::relayTransaction(const cryptonote::Transaction& transaction, const Callback& callback) { std::thread task(&INodeTrivialRefreshStub::doRelayTransaction, this, transaction, callback); task.detach(); } -void INodeTrivialRefreshStub::doRelayTransaction(const cryptonote::transaction& transaction, const Callback& callback) +void INodeTrivialRefreshStub::doRelayTransaction(const cryptonote::Transaction& transaction, const Callback& callback) { if (m_nextTxError) { @@ -122,7 +122,7 @@ void INodeTrivialRefreshStub::doGetRandomOutsByAmounts(std::vector amo void INodeTrivialRefreshStub::startAlternativeChain(uint64_t height) { - std::vector& blockchain = m_blockchainGenerator.getBlockchain(); + std::vector& blockchain = m_blockchainGenerator.getBlockchain(); assert(height < blockchain.size()); assert(height > m_lastHeight); diff --git a/tests/unit_tests/INodeStubs.h b/tests/unit_tests/INodeStubs.h index b25e2a58..60ce379e 100644 --- a/tests/unit_tests/INodeStubs.h +++ b/tests/unit_tests/INodeStubs.h @@ -38,7 +38,7 @@ public: virtual void getNewBlocks(std::list&& knownBlockIds, std::list& newBlocks, uint64_t& startHeight, const Callback& callback) {callback(std::error_code());}; - virtual void relayTransaction(const cryptonote::transaction& transaction, const Callback& callback) {callback(std::error_code());}; + virtual void relayTransaction(const cryptonote::Transaction& transaction, const Callback& callback) {callback(std::error_code());}; virtual void getRandomOutsByAmounts(std::vector&& amounts, uint64_t outsCount, std::vector& result, const Callback& callback) {callback(std::error_code());}; virtual void getTransactionOutsGlobalIndices(const crypto::hash& transactionHash, std::vector& outsGlobalIndices, const Callback& callback) { callback(std::error_code()); }; }; @@ -53,7 +53,7 @@ public: virtual void getNewBlocks(std::list&& knownBlockIds, std::list& newBlocks, uint64_t& startHeight, const Callback& callback); - virtual void relayTransaction(const cryptonote::transaction& transaction, const Callback& callback); + virtual void relayTransaction(const cryptonote::Transaction& transaction, const Callback& callback); virtual void getRandomOutsByAmounts(std::vector&& amounts, uint64_t outsCount, std::vector& result, const Callback& callback); virtual void getTransactionOutsGlobalIndices(const crypto::hash& transactionHash, std::vector& outsGlobalIndices, const Callback& callback); @@ -63,7 +63,7 @@ public: private: void doGetNewBlocks(std::list knownBlockIds, std::list& newBlocks, uint64_t& startHeight, const Callback& callback); void doGetTransactionOutsGlobalIndices(const crypto::hash& transactionHash, std::vector& outsGlobalIndices, const Callback& callback); - void doRelayTransaction(const cryptonote::transaction& transaction, const Callback& callback); + void doRelayTransaction(const cryptonote::Transaction& transaction, const Callback& callback); void doGetRandomOutsByAmounts(std::vector amounts, uint64_t outsCount, std::vector& result, const Callback& callback); uint64_t m_lastHeight; diff --git a/tests/unit_tests/TestBlockchainGenerator.cpp b/tests/unit_tests/TestBlockchainGenerator.cpp index 41ac13d7..92d5e619 100644 --- a/tests/unit_tests/TestBlockchainGenerator.cpp +++ b/tests/unit_tests/TestBlockchainGenerator.cpp @@ -31,7 +31,7 @@ public: return base_class::init(); } - void generate(const cryptonote::account_public_address& address, cryptonote::transaction& tx) + void generate(const cryptonote::AccountPublicAddress& address, cryptonote::Transaction& tx) { cryptonote::tx_destination_entry destination(this->m_source_amount, address); std::vector destinations; @@ -42,18 +42,20 @@ public: }; -TestBlockchainGenerator::TestBlockchainGenerator() +TestBlockchainGenerator::TestBlockchainGenerator(const cryptonote::Currency& currency) : + m_currency(currency), + generator(currency) { miner_acc.generate(); addGenesisBlock(); } -std::vector& TestBlockchainGenerator::getBlockchain() +std::vector& TestBlockchainGenerator::getBlockchain() { return m_blockchain; } -bool TestBlockchainGenerator::getTransactionByHash(const crypto::hash& hash, cryptonote::transaction& tx) +bool TestBlockchainGenerator::getTransactionByHash(const crypto::hash& hash, cryptonote::Transaction& tx) { auto it = m_txs.find(hash); if (it == m_txs.end()) @@ -65,10 +67,10 @@ bool TestBlockchainGenerator::getTransactionByHash(const crypto::hash& hash, cry void TestBlockchainGenerator::addGenesisBlock() { - cryptonote::block genesis; + cryptonote::Block genesis; uint64_t timestamp = time(NULL); - generator.construct_block(genesis, miner_acc, timestamp); + generator.constructBlock(genesis, miner_acc, timestamp); m_blockchain.push_back(genesis); } @@ -78,47 +80,47 @@ void TestBlockchainGenerator::generateEmptyBlocks(size_t count) for (size_t i = 0; i < count; ++i) { - cryptonote::block& prev_block = m_blockchain.back(); - cryptonote::block block; - generator.construct_block(block, prev_block, miner_acc); + cryptonote::Block& prev_block = m_blockchain.back(); + cryptonote::Block block; + generator.constructBlock(block, prev_block, miner_acc); m_blockchain.push_back(block); } } -void TestBlockchainGenerator::addTxToBlockchain(const cryptonote::transaction& transaction) +void TestBlockchainGenerator::addTxToBlockchain(const cryptonote::Transaction& transaction) { crypto::hash txHash = cryptonote::get_transaction_hash(transaction); m_txs[txHash] = transaction; - std::list txs; + std::list txs; txs.push_back(transaction); - cryptonote::block& prev_block = m_blockchain.back(); - cryptonote::block block; + cryptonote::Block& prev_block = m_blockchain.back(); + cryptonote::Block block; - generator.construct_block(block, prev_block, miner_acc, txs); + generator.constructBlock(block, prev_block, miner_acc, txs); m_blockchain.push_back(block); } -bool TestBlockchainGenerator::getBlockRewardForAddress(const cryptonote::account_public_address& address) +bool TestBlockchainGenerator::getBlockRewardForAddress(const cryptonote::AccountPublicAddress& address) { TransactionForAddressCreator creator; if (!creator.init()) return false; - cryptonote::transaction tx; + cryptonote::Transaction tx; creator.generate(address, tx); crypto::hash txHash = cryptonote::get_transaction_hash(tx); m_txs[txHash] = tx; - std::list txs; + std::list txs; txs.push_back(tx); - cryptonote::block& prev_block = m_blockchain.back(); - cryptonote::block block; + cryptonote::Block& prev_block = m_blockchain.back(); + cryptonote::Block block; - generator.construct_block(block, prev_block, miner_acc, txs); + generator.constructBlock(block, prev_block, miner_acc, txs); m_blockchain.push_back(block); return true; diff --git a/tests/unit_tests/TestBlockchainGenerator.h b/tests/unit_tests/TestBlockchainGenerator.h index 80fd5432..6863d3b8 100644 --- a/tests/unit_tests/TestBlockchainGenerator.h +++ b/tests/unit_tests/TestBlockchainGenerator.h @@ -17,29 +17,32 @@ #pragma once -#include "../core_tests/chaingen.h" #include #include #include "cryptonote_core/account.h" #include "cryptonote_core/cryptonote_basic.h" +#include "cryptonote_core/Currency.h" #include "crypto/hash.h" +#include "../TestGenerator/TestGenerator.h" + class TestBlockchainGenerator { public: - TestBlockchainGenerator(); + TestBlockchainGenerator(const cryptonote::Currency& currency); - std::vector& getBlockchain(); + std::vector& getBlockchain(); void addGenesisBlock(); void generateEmptyBlocks(size_t count); - bool getBlockRewardForAddress(const cryptonote::account_public_address& address); - void addTxToBlockchain(const cryptonote::transaction& transaction); - bool getTransactionByHash(const crypto::hash& hash, cryptonote::transaction& tx); + bool getBlockRewardForAddress(const cryptonote::AccountPublicAddress& address); + void addTxToBlockchain(const cryptonote::Transaction& transaction); + bool getTransactionByHash(const crypto::hash& hash, cryptonote::Transaction& tx); private: + const cryptonote::Currency& m_currency; test_generator generator; cryptonote::account_base miner_acc; - std::vector m_blockchain; - std::unordered_map m_txs; + std::vector m_blockchain; + std::unordered_map m_txs; }; diff --git a/tests/unit_tests/TestUpgradeDetector.cpp b/tests/unit_tests/TestUpgradeDetector.cpp new file mode 100644 index 00000000..a8146a64 --- /dev/null +++ b/tests/unit_tests/TestUpgradeDetector.cpp @@ -0,0 +1,309 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include + +#include "gtest/gtest.h" + +#include "cryptonote_core/cryptonote_basic.h" +#include "cryptonote_core/UpgradeDetector.h" + +namespace { + using cryptonote::BLOCK_MAJOR_VERSION_1; + using cryptonote::BLOCK_MAJOR_VERSION_2; + using cryptonote::BLOCK_MINOR_VERSION_0; + using cryptonote::BLOCK_MINOR_VERSION_1; + + struct BlockEx { + cryptonote::Block bl; + }; + + typedef std::vector BlockVector; + typedef cryptonote::BasicUpgradeDetector UpgradeDetector; + + cryptonote::Currency createCurrency(uint64_t upgradeHeight = UpgradeDetector::UNDEF_HEIGHT) { + cryptonote::CurrencyBuilder currencyBuilder; + currencyBuilder.upgradeVotingThreshold(90); + currencyBuilder.upgradeVotingWindow(720); + currencyBuilder.upgradeWindow(720); + currencyBuilder.upgradeHeight(upgradeHeight); + return currencyBuilder.currency(); + } + + void createBlocks(BlockVector& blockchain, size_t count, uint8_t majorVersion, uint8_t minorVersion) { + for (size_t i = 0; i < count; ++i) { + BlockEx b; + b.bl.majorVersion = majorVersion; + b.bl.minorVersion = minorVersion; + b.bl.timestamp = 0; + blockchain.push_back(b); + } + } + + void createBlocks(BlockVector& blockchain, UpgradeDetector& upgradeDetector, size_t count, uint8_t majorVersion, uint8_t minorVersion) { + for (size_t i = 0; i < count; ++i) { + BlockEx b; + b.bl.majorVersion = majorVersion; + b.bl.minorVersion = minorVersion; + b.bl.timestamp = 0; + blockchain.push_back(b); + upgradeDetector.blockPushed(); + } + } + + void popBlocks(BlockVector& blockchain, UpgradeDetector& upgradeDetector, size_t count) { + for (size_t i = 0; i < count; ++i) { + blockchain.pop_back(); + upgradeDetector.blockPopped(); + } + } + + + TEST(UpgradeDetector_voting_init, handlesEmptyBlockchain) { + cryptonote::Currency currency = createCurrency(); + BlockVector blocks; + UpgradeDetector upgradeDetector(currency, blocks, BLOCK_MAJOR_VERSION_2); + ASSERT_TRUE(upgradeDetector.init()); + ASSERT_EQ(upgradeDetector.votingCompleteHeight(), UpgradeDetector::UNDEF_HEIGHT); + } + + TEST(UpgradeDetector_voting_init, votingIsNotCompleteDueShortBlockchain) { + cryptonote::Currency currency = createCurrency(); + BlockVector blocks; + createBlocks(blocks, currency.upgradeVotingWindow() - 1, BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_1); + + UpgradeDetector upgradeDetector(currency, blocks, BLOCK_MAJOR_VERSION_2); + ASSERT_TRUE(upgradeDetector.init()); + ASSERT_EQ(upgradeDetector.votingCompleteHeight(), UpgradeDetector::UNDEF_HEIGHT); + } + + TEST(UpgradeDetector_voting_init, votingIsCompleteAfterMinimumNumberOfBlocks) { + cryptonote::Currency currency = createCurrency(); + BlockVector blocks; + createBlocks(blocks, currency.upgradeVotingWindow(), BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_1); + + UpgradeDetector upgradeDetector(currency, blocks, BLOCK_MAJOR_VERSION_2); + ASSERT_TRUE(upgradeDetector.init()); + ASSERT_EQ(upgradeDetector.votingCompleteHeight(), currency.upgradeVotingWindow() - 1); + } + + TEST(UpgradeDetector_voting_init, votingIsNotCompleteDueLackOfVoices) { + cryptonote::Currency currency = createCurrency(); + BlockVector blocks; + createBlocks(blocks, currency.upgradeVotingWindow(), BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_0); + createBlocks(blocks, currency.minNumberVotingBlocks() - 1, BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_1); + + UpgradeDetector upgradeDetector(currency, blocks, BLOCK_MAJOR_VERSION_2); + ASSERT_TRUE(upgradeDetector.init()); + ASSERT_EQ(upgradeDetector.votingCompleteHeight(), UpgradeDetector::UNDEF_HEIGHT); + } + + TEST(UpgradeDetector_voting_init, votingIsCompleteAfterMinimumNumberOfVoices) { + cryptonote::Currency currency = createCurrency(); + BlockVector blocks; + createBlocks(blocks, currency.upgradeVotingWindow(), BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_0); + createBlocks(blocks, currency.minNumberVotingBlocks(), BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_1); + + UpgradeDetector upgradeDetector(currency, blocks, BLOCK_MAJOR_VERSION_2); + ASSERT_TRUE(upgradeDetector.init()); + ASSERT_EQ(upgradeDetector.votingCompleteHeight(), blocks.size() - 1); + } + + TEST(UpgradeDetector_voting_init, handlesOneCompleteUpgrade) { + cryptonote::Currency currency = createCurrency(); + BlockVector blocks; + createBlocks(blocks, currency.upgradeVotingWindow(), BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_1); + uint64_t upgradeHeight = currency.calculateUpgradeHeight(blocks.size() - 1); + createBlocks(blocks, upgradeHeight - blocks.size(), BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_0); + // Upgrade is here + createBlocks(blocks, 1, BLOCK_MAJOR_VERSION_2, BLOCK_MINOR_VERSION_0); + + UpgradeDetector upgradeDetector(currency, blocks, BLOCK_MAJOR_VERSION_2); + ASSERT_TRUE(upgradeDetector.init()); + ASSERT_EQ(upgradeDetector.votingCompleteHeight(), currency.upgradeVotingWindow() - 1); + ASSERT_EQ(upgradeDetector.upgradeHeight(), upgradeHeight); + } + + TEST(UpgradeDetector_voting_init, handlesAFewCompleteUpgrades) { + cryptonote::Currency currency = createCurrency(); + const uint8_t BLOCK_V3 = BLOCK_MAJOR_VERSION_2 + 1; + const uint8_t BLOCK_V4 = BLOCK_MAJOR_VERSION_2 + 2; + + BlockVector blocks; + + createBlocks(blocks, currency.upgradeVotingWindow(), BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_1); + uint64_t votingCompleteHeigntV2 = blocks.size() - 1; + uint64_t upgradeHeightV2 = currency.calculateUpgradeHeight(votingCompleteHeigntV2); + createBlocks(blocks, upgradeHeightV2 - blocks.size(), BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_0); + // Upgrade to v2 is here + createBlocks(blocks, 1, BLOCK_MAJOR_VERSION_2, BLOCK_MINOR_VERSION_0); + + createBlocks(blocks, currency.upgradeVotingWindow(), BLOCK_MAJOR_VERSION_2, BLOCK_MINOR_VERSION_1); + uint64_t votingCompleteHeigntV3 = blocks.size() - 1; + uint64_t upgradeHeightV3 = currency.calculateUpgradeHeight(votingCompleteHeigntV3); + createBlocks(blocks, upgradeHeightV3 - blocks.size(), BLOCK_MAJOR_VERSION_2, BLOCK_MINOR_VERSION_0); + // Upgrade to v3 is here + createBlocks(blocks, 1, BLOCK_V3, BLOCK_MINOR_VERSION_0); + + createBlocks(blocks, currency.upgradeVotingWindow(), BLOCK_V3, BLOCK_MINOR_VERSION_1); + uint64_t votingCompleteHeigntV4 = blocks.size() - 1; + uint64_t upgradeHeightV4 = currency.calculateUpgradeHeight(votingCompleteHeigntV4); + createBlocks(blocks, upgradeHeightV4 - blocks.size(), BLOCK_V3, BLOCK_MINOR_VERSION_0); + // Upgrade to v4 is here + createBlocks(blocks, 1, BLOCK_V4, BLOCK_MINOR_VERSION_0); + + UpgradeDetector upgradeDetectorV2(currency, blocks, BLOCK_MAJOR_VERSION_2); + ASSERT_TRUE(upgradeDetectorV2.init()); + ASSERT_EQ(upgradeDetectorV2.votingCompleteHeight(), votingCompleteHeigntV2); + ASSERT_EQ(upgradeDetectorV2.upgradeHeight(), upgradeHeightV2); + + UpgradeDetector upgradeDetectorV3(currency, blocks, BLOCK_V3); + ASSERT_TRUE(upgradeDetectorV3.init()); + ASSERT_EQ(upgradeDetectorV3.votingCompleteHeight(), votingCompleteHeigntV3); + ASSERT_EQ(upgradeDetectorV3.upgradeHeight(), upgradeHeightV3); + + UpgradeDetector upgradeDetectorV4(currency, blocks, BLOCK_V4); + ASSERT_TRUE(upgradeDetectorV4.init()); + ASSERT_EQ(upgradeDetectorV4.votingCompleteHeight(), votingCompleteHeigntV4); + ASSERT_EQ(upgradeDetectorV4.upgradeHeight(), upgradeHeightV4); + } + + TEST(UpgradeDetector_upgradeHeight_init, handlesEmptyBlockchain) { + const uint64_t upgradeHeight = 17; + cryptonote::Currency currency = createCurrency(upgradeHeight); + BlockVector blocks; + UpgradeDetector upgradeDetector(currency, blocks, BLOCK_MAJOR_VERSION_2); + ASSERT_TRUE(upgradeDetector.init()); + ASSERT_EQ(upgradeDetector.upgradeHeight(), upgradeHeight); + ASSERT_EQ(upgradeDetector.votingCompleteHeight(), UpgradeDetector::UNDEF_HEIGHT); + } + + TEST(UpgradeDetector_upgradeHeight_init, handlesBlockchainBeforeUpgrade) { + const uint64_t upgradeHeight = 17; + cryptonote::Currency currency = createCurrency(upgradeHeight); + BlockVector blocks; + createBlocks(blocks, upgradeHeight, BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_1); + + UpgradeDetector upgradeDetector(currency, blocks, BLOCK_MAJOR_VERSION_2); + ASSERT_TRUE(upgradeDetector.init()); + ASSERT_EQ(upgradeDetector.upgradeHeight(), upgradeHeight); + ASSERT_EQ(upgradeDetector.votingCompleteHeight(), UpgradeDetector::UNDEF_HEIGHT); + } + + TEST(UpgradeDetector_upgradeHeight_init, handlesBlockchainAtUpgrade) { + const uint64_t upgradeHeight = 17; + cryptonote::Currency currency = createCurrency(upgradeHeight); + BlockVector blocks; + createBlocks(blocks, upgradeHeight + 1, BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_1); + + UpgradeDetector upgradeDetector(currency, blocks, BLOCK_MAJOR_VERSION_2); + ASSERT_TRUE(upgradeDetector.init()); + ASSERT_EQ(upgradeDetector.upgradeHeight(), upgradeHeight); + ASSERT_EQ(upgradeDetector.votingCompleteHeight(), UpgradeDetector::UNDEF_HEIGHT); + } + + TEST(UpgradeDetector_upgradeHeight_init, handlesBlockchainAfterUpgrade) { + const uint64_t upgradeHeight = 17; + cryptonote::Currency currency = createCurrency(upgradeHeight); + BlockVector blocks; + createBlocks(blocks, upgradeHeight + 1, BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_1); + createBlocks(blocks, 1, BLOCK_MAJOR_VERSION_2, BLOCK_MINOR_VERSION_0); + + UpgradeDetector upgradeDetector(currency, blocks, BLOCK_MAJOR_VERSION_2); + ASSERT_TRUE(upgradeDetector.init()); + ASSERT_EQ(upgradeDetector.upgradeHeight(), upgradeHeight); + ASSERT_EQ(upgradeDetector.votingCompleteHeight(), UpgradeDetector::UNDEF_HEIGHT); + } + + TEST(UpgradeDetector_voting, handlesVotingCompleteStartingEmptyBlockchain) { + cryptonote::Currency currency = createCurrency(); + BlockVector blocks; + UpgradeDetector upgradeDetector(currency, blocks, BLOCK_MAJOR_VERSION_2); + ASSERT_TRUE(upgradeDetector.init()); + + createBlocks(blocks, upgradeDetector, currency.upgradeVotingWindow(), BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_0); + createBlocks(blocks, upgradeDetector, currency.minNumberVotingBlocks(), BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_1); + ASSERT_EQ(upgradeDetector.votingCompleteHeight(), blocks.size() - 1); + } + + TEST(UpgradeDetector_voting, handlesVotingCompleteStartingNonEmptyBlockchain) { + cryptonote::Currency currency = createCurrency(); + assert(currency.minNumberVotingBlocks() >= 2); + const uint64_t portion = currency.minNumberVotingBlocks() - currency.minNumberVotingBlocks() / 2; + + BlockVector blocks; + UpgradeDetector upgradeDetector(currency, blocks, BLOCK_MAJOR_VERSION_2); + + createBlocks(blocks, upgradeDetector, currency.upgradeVotingWindow(), BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_0); + createBlocks(blocks, upgradeDetector, currency.minNumberVotingBlocks() - portion, BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_1); + + ASSERT_TRUE(upgradeDetector.init()); + createBlocks(blocks, upgradeDetector, portion, BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_1); + ASSERT_EQ(upgradeDetector.votingCompleteHeight(), blocks.size() - 1); + } + + TEST(UpgradeDetector_voting, handlesVotingCancelling) { + cryptonote::Currency currency = createCurrency(); + BlockVector blocks; + UpgradeDetector upgradeDetector(currency, blocks, BLOCK_MAJOR_VERSION_2); + ASSERT_TRUE(upgradeDetector.init()); + + createBlocks(blocks, upgradeDetector, currency.upgradeVotingWindow(), BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_0); + createBlocks(blocks, upgradeDetector, currency.minNumberVotingBlocks(), BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_1); + uint64_t votingCompleteHeight = blocks.size() - 1; + uint64_t hadrforkHeight = currency.calculateUpgradeHeight(votingCompleteHeight); + ASSERT_EQ(upgradeDetector.votingCompleteHeight(), votingCompleteHeight); + + createBlocks(blocks, upgradeDetector, hadrforkHeight - votingCompleteHeight - 1, BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_0); + ASSERT_EQ(upgradeDetector.votingCompleteHeight(), votingCompleteHeight); + + // Cancel voting + popBlocks(blocks, upgradeDetector, hadrforkHeight - votingCompleteHeight - 1); + ASSERT_EQ(upgradeDetector.votingCompleteHeight(), votingCompleteHeight); + popBlocks(blocks, upgradeDetector, 1); + ASSERT_EQ(upgradeDetector.votingCompleteHeight(), UpgradeDetector::UNDEF_HEIGHT); + } + + TEST(UpgradeDetector_voting, handlesVotingAndUpgradeCancelling) { + cryptonote::Currency currency = createCurrency(); + BlockVector blocks; + UpgradeDetector upgradeDetector(currency, blocks, BLOCK_MAJOR_VERSION_2); + ASSERT_TRUE(upgradeDetector.init()); + + createBlocks(blocks, upgradeDetector, currency.upgradeVotingWindow(), BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_0); + createBlocks(blocks, upgradeDetector, currency.minNumberVotingBlocks(), BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_1); + uint64_t votingCompleteHeight = blocks.size() - 1; + uint64_t hadrforkHeight = currency.calculateUpgradeHeight(votingCompleteHeight); + ASSERT_EQ(votingCompleteHeight, upgradeDetector.votingCompleteHeight()); + + createBlocks(blocks, upgradeDetector, hadrforkHeight - votingCompleteHeight, BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_0); + createBlocks(blocks, upgradeDetector, 1, BLOCK_MAJOR_VERSION_2, BLOCK_MINOR_VERSION_0); + ASSERT_EQ(votingCompleteHeight, upgradeDetector.votingCompleteHeight()); + + // Cancel upgrade (pop block v2) + popBlocks(blocks, upgradeDetector, 1); + ASSERT_EQ(votingCompleteHeight, upgradeDetector.votingCompleteHeight()); + + // Pop blocks after voting + popBlocks(blocks, upgradeDetector, hadrforkHeight - votingCompleteHeight); + ASSERT_EQ(votingCompleteHeight, upgradeDetector.votingCompleteHeight()); + + // Cancel voting + popBlocks(blocks, upgradeDetector, 1); + ASSERT_EQ(UpgradeDetector::UNDEF_HEIGHT, upgradeDetector.votingCompleteHeight()); + } +} diff --git a/tests/unit_tests/base58.cpp b/tests/unit_tests/base58.cpp index ecf127d7..20452ba6 100644 --- a/tests/unit_tests/base58.cpp +++ b/tests/unit_tests/base58.cpp @@ -454,73 +454,75 @@ namespace "\x22\x09\x39\x68\x9e\xdf\x1a\xbd\x5b\xc1\xd0\x31\xf7\x3e\xcd\x6c" "\x99\x3a\xdd\x66\xd6\x80\x88\x70\x45\x6a\xfe\xb8\xe7\xee\xb6\x8d"); std::string test_keys_addr_str = "2AaF4qEmER6dNeM6dfiBFL7kqund3HYGvMBF3ttsNd9SfzgYB6L7ep1Yg1osYJzLdaKAYSLVh6e6jKnAuzj3bw1oGyd1x7Z"; + const uint64_t TEST_PUBLIC_ADDRESS_BASE58_PREFIX = 6; } -TEST(get_account_address_as_str, works_correctly) +TEST(getAccountAddressAsStr, works_correctly) { - cryptonote::account_public_address addr; + cryptonote::AccountPublicAddress addr; ASSERT_TRUE(serialization::parse_binary(test_serialized_keys, addr)); - std::string addr_str = cryptonote::get_account_address_as_str(addr); + std::string addr_str = cryptonote::getAccountAddressAsStr(TEST_PUBLIC_ADDRESS_BASE58_PREFIX, addr); ASSERT_EQ(addr_str, test_keys_addr_str); } -TEST(get_account_address_from_str, handles_valid_address) +TEST(parseAccountAddressString, handles_valid_address) { - cryptonote::account_public_address addr; - ASSERT_TRUE(cryptonote::get_account_address_from_str(addr, test_keys_addr_str)); + uint64_t prefix; + cryptonote::AccountPublicAddress addr; + ASSERT_TRUE(cryptonote::parseAccountAddressString(prefix, addr, test_keys_addr_str)); + ASSERT_EQ(TEST_PUBLIC_ADDRESS_BASE58_PREFIX, prefix); std::string blob; ASSERT_TRUE(serialization::dump_binary(addr, blob)); ASSERT_EQ(blob, test_serialized_keys); } -TEST(get_account_address_from_str, fails_on_invalid_address_format) +TEST(parseAccountAddressString, fails_on_invalid_address_format) { - cryptonote::account_public_address addr; std::string addr_str = test_keys_addr_str; addr_str[0] = '0'; - ASSERT_FALSE(cryptonote::get_account_address_from_str(addr, addr_str)); + uint64_t prefix; + cryptonote::AccountPublicAddress addr; + ASSERT_FALSE(cryptonote::parseAccountAddressString(prefix, addr, addr_str)); } -TEST(get_account_address_from_str, fails_on_invalid_address_prefix) +TEST(parseAccountAddressString, fails_on_invalid_address_prefix) { std::string addr_str = base58::encode_addr(0, test_serialized_keys); - cryptonote::account_public_address addr; - ASSERT_FALSE(cryptonote::get_account_address_from_str(addr, addr_str)); + uint64_t prefix; + cryptonote::AccountPublicAddress addr; + ASSERT_FALSE(cryptonote::parseAccountAddressString(prefix, addr, addr_str)); } -TEST(get_account_address_from_str, fails_on_invalid_address_content) +TEST(parseAccountAddressString, fails_on_invalid_address_content) { - std::string addr_str = base58::encode_addr(CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, test_serialized_keys.substr(1)); + std::string addr_str = base58::encode_addr(TEST_PUBLIC_ADDRESS_BASE58_PREFIX, test_serialized_keys.substr(1)); - cryptonote::account_public_address addr; - ASSERT_FALSE(cryptonote::get_account_address_from_str(addr, addr_str)); + uint64_t prefix; + cryptonote::AccountPublicAddress addr; + ASSERT_FALSE(cryptonote::parseAccountAddressString(prefix, addr, addr_str)); } -TEST(get_account_address_from_str, fails_on_invalid_address_spend_key) +TEST(parseAccountAddressString, fails_on_invalid_address_spend_key) { std::string serialized_keys_copy = test_serialized_keys; serialized_keys_copy[0] = '\0'; - std::string addr_str = base58::encode_addr(CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, serialized_keys_copy); + std::string addr_str = base58::encode_addr(TEST_PUBLIC_ADDRESS_BASE58_PREFIX, serialized_keys_copy); - cryptonote::account_public_address addr; - ASSERT_FALSE(cryptonote::get_account_address_from_str(addr, addr_str)); + uint64_t prefix; + cryptonote::AccountPublicAddress addr; + ASSERT_FALSE(cryptonote::parseAccountAddressString(prefix, addr, addr_str)); } -TEST(get_account_address_from_str, fails_on_invalid_address_view_key) +TEST(parseAccountAddressString, fails_on_invalid_address_view_key) { std::string serialized_keys_copy = test_serialized_keys; serialized_keys_copy.back() = '\x01'; - std::string addr_str = base58::encode_addr(CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, serialized_keys_copy); + std::string addr_str = base58::encode_addr(TEST_PUBLIC_ADDRESS_BASE58_PREFIX, serialized_keys_copy); - cryptonote::account_public_address addr; - ASSERT_FALSE(cryptonote::get_account_address_from_str(addr, addr_str)); -} - -TEST(get_account_address_from_str, parses_old_address_format) -{ - cryptonote::account_public_address addr; - ASSERT_TRUE(cryptonote::get_account_address_from_str(addr, "002391bbbb24dea6fd95232e97594a27769d0153d053d2102b789c498f57a2b00b69cd6f2f5c529c1660f2f4a2b50178d6640c20ce71fe26373041af97c5b10236fc")); + uint64_t prefix; + cryptonote::AccountPublicAddress addr; + ASSERT_FALSE(cryptonote::parseAccountAddressString(prefix, addr, addr_str)); } diff --git a/tests/unit_tests/block_reward.cpp b/tests/unit_tests/block_reward.cpp index 39f9f68f..261e73ad 100644 --- a/tests/unit_tests/block_reward.cpp +++ b/tests/unit_tests/block_reward.cpp @@ -18,218 +18,400 @@ #include "gtest/gtest.h" #include "cryptonote_core/cryptonote_basic_impl.h" +#include "cryptonote_core/Currency.h" using namespace cryptonote; namespace { - //-------------------------------------------------------------------------------------------------------------------- - class block_reward_and_already_generated_coins : public ::testing::Test - { - protected: - static const size_t current_block_size = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE / 2; + const uint64_t TEST_GRANTED_FULL_REWARD_ZONE = 10000; + const uint64_t TEST_MONEY_SUPPLY = static_cast(-1); + const uint64_t TEST_EMISSION_SPEED_FACTOR = 18; - bool m_block_not_too_big; - uint64_t m_block_reward; + //-------------------------------------------------------------------------------------------------------------------- + class getBlockReward_and_already_generated_coins : public ::testing::Test { + public: + getBlockReward_and_already_generated_coins() : + ::testing::Test(), + m_currency(cryptonote::CurrencyBuilder(). + blockGrantedFullRewardZone(TEST_GRANTED_FULL_REWARD_ZONE). + moneySupply(TEST_MONEY_SUPPLY). + emissionSpeedFactor(TEST_EMISSION_SPEED_FACTOR). + currency()) { + } + + protected: + static const size_t currentBlockSize = TEST_GRANTED_FULL_REWARD_ZONE / 2; + + cryptonote::Currency m_currency; + bool m_blockTooBig; + int64_t m_emissionChange; + uint64_t m_blockReward; }; - #define TEST_ALREADY_GENERATED_COINS(already_generated_coins, expected_reward) \ - m_block_not_too_big = get_block_reward(0, current_block_size, already_generated_coins, m_block_reward); \ - ASSERT_TRUE(m_block_not_too_big); \ - ASSERT_EQ(m_block_reward, UINT64_C(expected_reward)); + #define TEST_ALREADY_GENERATED_COINS(alreadyGeneratedCoins, expectedReward) \ + m_blockTooBig = !m_currency.getBlockReward(0, currentBlockSize, alreadyGeneratedCoins, 0, false, \ + m_blockReward, m_emissionChange); \ + ASSERT_FALSE(m_blockTooBig); \ + ASSERT_EQ(UINT64_C(expectedReward), m_blockReward); \ + ASSERT_EQ(UINT64_C(expectedReward), m_emissionChange); - TEST_F(block_reward_and_already_generated_coins, handles_first_values) - { + TEST_F(getBlockReward_and_already_generated_coins, handles_first_values) { TEST_ALREADY_GENERATED_COINS(0, 70368744177663); - TEST_ALREADY_GENERATED_COINS(m_block_reward, 70368475742208); + TEST_ALREADY_GENERATED_COINS(m_blockReward, 70368475742208); TEST_ALREADY_GENERATED_COINS(UINT64_C(2756434948434199641), 59853779316998); } - TEST_F(block_reward_and_already_generated_coins, correctly_steps_from_2_to_1) - { - TEST_ALREADY_GENERATED_COINS(MONEY_SUPPLY - ((2 << 18) + 1), 2); - TEST_ALREADY_GENERATED_COINS(MONEY_SUPPLY - (2 << 18) , 2); - TEST_ALREADY_GENERATED_COINS(MONEY_SUPPLY - ((2 << 18) - 1), 1); + TEST_F(getBlockReward_and_already_generated_coins, correctly_steps_from_reward_2_to_1) { + TEST_ALREADY_GENERATED_COINS(m_currency.moneySupply() - ((UINT64_C(2) << m_currency.emissionSpeedFactor()) + 1), 2); + TEST_ALREADY_GENERATED_COINS(m_currency.moneySupply() - (UINT64_C(2) << m_currency.emissionSpeedFactor()) , 2); + TEST_ALREADY_GENERATED_COINS(m_currency.moneySupply() - ((UINT64_C(2) << m_currency.emissionSpeedFactor()) - 1), 1); } - TEST_F(block_reward_and_already_generated_coins, handles_max) - { - TEST_ALREADY_GENERATED_COINS(MONEY_SUPPLY - ((1 << 18) + 1), 1); - TEST_ALREADY_GENERATED_COINS(MONEY_SUPPLY - (1 << 18) , 1); - TEST_ALREADY_GENERATED_COINS(MONEY_SUPPLY - ((1 << 18) - 1), 0); + TEST_F(getBlockReward_and_already_generated_coins, handles_max_already_generaged_coins) { + TEST_ALREADY_GENERATED_COINS(m_currency.moneySupply() - ((UINT64_C(1) << m_currency.emissionSpeedFactor()) + 1), 1); + TEST_ALREADY_GENERATED_COINS(m_currency.moneySupply() - (UINT64_C(1) << m_currency.emissionSpeedFactor()) , 1); + TEST_ALREADY_GENERATED_COINS(m_currency.moneySupply() - ((UINT64_C(1) << m_currency.emissionSpeedFactor()) - 1), 0); + TEST_ALREADY_GENERATED_COINS(m_currency.moneySupply() - 1, 0); + TEST_ALREADY_GENERATED_COINS(m_currency.moneySupply(), 0); } //-------------------------------------------------------------------------------------------------------------------- - class block_reward_and_current_block_size : public ::testing::Test - { + class getBlockReward_and_median_and_blockSize : public ::testing::Test { + public: + getBlockReward_and_median_and_blockSize() : + ::testing::Test(), + m_currency(cryptonote::CurrencyBuilder(). + blockGrantedFullRewardZone(TEST_GRANTED_FULL_REWARD_ZONE). + moneySupply(TEST_MONEY_SUPPLY). + emissionSpeedFactor(TEST_EMISSION_SPEED_FACTOR). + currency()) { + } + protected: - virtual void SetUp() - { - m_block_not_too_big = get_block_reward(0, 0, already_generated_coins, m_standard_block_reward); - ASSERT_TRUE(m_block_not_too_big); - ASSERT_LT(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE, m_standard_block_reward); + static const uint64_t alreadyGeneratedCoins = 0; + + virtual void SetUp() { + m_blockTooBig = !m_currency.getBlockReward(0, 0, alreadyGeneratedCoins, 0, false, + m_standardBlockReward, m_emissionChange); + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(UINT64_C(70368744177663), m_standardBlockReward); } - void do_test(size_t median_block_size, size_t current_block_size) - { - m_block_not_too_big = get_block_reward(median_block_size, current_block_size, already_generated_coins, m_block_reward); + void do_test(size_t medianBlockSize, size_t currentBlockSize) { + m_blockTooBig = !m_currency.getBlockReward(medianBlockSize, currentBlockSize, alreadyGeneratedCoins, 0, false, + m_blockReward, m_emissionChange); } - static const uint64_t already_generated_coins = 0; - - bool m_block_not_too_big; - uint64_t m_block_reward; - uint64_t m_standard_block_reward; + cryptonote::Currency m_currency; + bool m_blockTooBig; + int64_t m_emissionChange; + uint64_t m_blockReward; + uint64_t m_standardBlockReward; }; - TEST_F(block_reward_and_current_block_size, handles_block_size_less_relevance_level) - { - do_test(0, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE - 1); - ASSERT_TRUE(m_block_not_too_big); - ASSERT_EQ(m_block_reward, m_standard_block_reward); + TEST_F(getBlockReward_and_median_and_blockSize, handles_zero_median) { + do_test(0, TEST_GRANTED_FULL_REWARD_ZONE); + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(m_standardBlockReward, m_blockReward); } - TEST_F(block_reward_and_current_block_size, handles_block_size_eq_relevance_level) - { - do_test(0, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE); - ASSERT_TRUE(m_block_not_too_big); - ASSERT_EQ(m_block_reward, m_standard_block_reward); + TEST_F(getBlockReward_and_median_and_blockSize, handles_median_lt_relevance_level) { + do_test(TEST_GRANTED_FULL_REWARD_ZONE - 1, TEST_GRANTED_FULL_REWARD_ZONE); + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(m_standardBlockReward, m_blockReward); } - TEST_F(block_reward_and_current_block_size, handles_block_size_gt_relevance_level) - { - do_test(0, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE + 1); - ASSERT_TRUE(m_block_not_too_big); - ASSERT_LT(m_block_reward, m_standard_block_reward); + TEST_F(getBlockReward_and_median_and_blockSize, handles_median_eq_relevance_level) { + do_test(TEST_GRANTED_FULL_REWARD_ZONE, TEST_GRANTED_FULL_REWARD_ZONE - 1); + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(m_standardBlockReward, m_blockReward); } - TEST_F(block_reward_and_current_block_size, handles_block_size_less_2_relevance_level) - { - do_test(0, 2 * CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE - 1); - ASSERT_TRUE(m_block_not_too_big); - ASSERT_LT(m_block_reward, m_standard_block_reward); - ASSERT_LT(0, m_block_reward); + TEST_F(getBlockReward_and_median_and_blockSize, handles_median_gt_relevance_level) { + do_test(TEST_GRANTED_FULL_REWARD_ZONE + 1, TEST_GRANTED_FULL_REWARD_ZONE); + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(m_standardBlockReward, m_blockReward); } - TEST_F(block_reward_and_current_block_size, handles_block_size_eq_2_relevance_level) - { - do_test(0, 2 * CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE); - ASSERT_TRUE(m_block_not_too_big); - ASSERT_EQ(0, m_block_reward); + TEST_F(getBlockReward_and_median_and_blockSize, handles_big_median) { + size_t blockSize = 1; + size_t medianSize = std::numeric_limits::max(); + + do_test(medianSize, blockSize); + + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(m_standardBlockReward, m_blockReward); } - TEST_F(block_reward_and_current_block_size, handles_block_size_gt_2_relevance_level) - { - do_test(0, 2 * CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE + 1); - ASSERT_FALSE(m_block_not_too_big); + TEST_F(getBlockReward_and_median_and_blockSize, handles_big_block_size) { + size_t blockSize = std::numeric_limits::max() - 1; // even + size_t medianSize = blockSize / 2; // 2 * medianSize == blockSize + + do_test(medianSize, blockSize); + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(0, m_blockReward); } - TEST_F(block_reward_and_current_block_size, fails_on_huge_median_size) - { -#if !defined(NDEBUG) - size_t huge_size = std::numeric_limits::max() + UINT64_C(2); - ASSERT_DEATH(do_test(huge_size, huge_size + 1), ""); -#endif + TEST_F(getBlockReward_and_median_and_blockSize, handles_big_block_size_fail) { + size_t blockSize = std::numeric_limits::max(); + size_t medianSize = blockSize / 2 - 1; + + do_test(medianSize, blockSize); + ASSERT_TRUE(m_blockTooBig); } - TEST_F(block_reward_and_current_block_size, fails_on_huge_block_size) - { -#if !defined(NDEBUG) - size_t huge_size = std::numeric_limits::max() + UINT64_C(2); - ASSERT_DEATH(do_test(huge_size - 2, huge_size), ""); -#endif + TEST_F(getBlockReward_and_median_and_blockSize, handles_big_median_and_block_size) { + // blockSize should be greater medianSize + size_t blockSize = std::numeric_limits::max(); + size_t medianSize = std::numeric_limits::max() - 1; + + do_test(medianSize, blockSize); + ASSERT_FALSE(m_blockTooBig); + ASSERT_LT(m_blockReward, m_standardBlockReward); } //-------------------------------------------------------------------------------------------------------------------- - class block_reward_and_last_block_sizes : public ::testing::Test - { + class getBlockReward_and_currentBlockSize : public ::testing::Test { + public: + getBlockReward_and_currentBlockSize() : + ::testing::Test(), + m_currency(cryptonote::CurrencyBuilder(). + blockGrantedFullRewardZone(TEST_GRANTED_FULL_REWARD_ZONE). + moneySupply(TEST_MONEY_SUPPLY). + emissionSpeedFactor(TEST_EMISSION_SPEED_FACTOR). + currency()) { + } + protected: - virtual void SetUp() - { - m_last_block_sizes.push_back(3 * CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE); - m_last_block_sizes.push_back(5 * CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE); - m_last_block_sizes.push_back(7 * CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE); - m_last_block_sizes.push_back(11 * CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE); - m_last_block_sizes.push_back(13 * CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE); + static const size_t testMedian = 7 * TEST_GRANTED_FULL_REWARD_ZONE; + static const uint64_t alreadyGeneratedCoins = 0; - m_last_block_sizes_median = 7 * CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE; + virtual void SetUp() { + m_blockTooBig = !m_currency.getBlockReward(testMedian, 0, alreadyGeneratedCoins, 0, false, + m_standardBlockReward, m_emissionChange); - m_block_not_too_big = get_block_reward(epee::misc_utils::median(m_last_block_sizes), 0, already_generated_coins, m_standard_block_reward); - ASSERT_TRUE(m_block_not_too_big); - ASSERT_LT(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE, m_standard_block_reward); + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(UINT64_C(70368744177663), m_standardBlockReward); } - void do_test(size_t current_block_size) - { - m_block_not_too_big = get_block_reward(epee::misc_utils::median(m_last_block_sizes), current_block_size, already_generated_coins, m_block_reward); + void do_test(size_t currentBlockSize) { + m_blockTooBig = !m_currency.getBlockReward(testMedian, currentBlockSize, alreadyGeneratedCoins, 0, false, + m_blockReward, m_emissionChange); } - static const uint64_t already_generated_coins = 0; - - std::vector m_last_block_sizes; - uint64_t m_last_block_sizes_median; - bool m_block_not_too_big; - uint64_t m_block_reward; - uint64_t m_standard_block_reward; + cryptonote::Currency m_currency; + bool m_blockTooBig; + int64_t m_emissionChange; + uint64_t m_blockReward; + uint64_t m_standardBlockReward; }; - TEST_F(block_reward_and_last_block_sizes, handles_block_size_less_median) - { - do_test(m_last_block_sizes_median - 1); - ASSERT_TRUE(m_block_not_too_big); - ASSERT_EQ(m_block_reward, m_standard_block_reward); + TEST_F(getBlockReward_and_currentBlockSize, handles_zero_block_size) { + do_test(0); + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(m_standardBlockReward, m_blockReward); } - TEST_F(block_reward_and_last_block_sizes, handles_block_size_eq_median) - { - do_test(m_last_block_sizes_median); - ASSERT_TRUE(m_block_not_too_big); - ASSERT_EQ(m_block_reward, m_standard_block_reward); + TEST_F(getBlockReward_and_currentBlockSize, handles_block_size_less_median) { + do_test(testMedian - 1); + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(m_standardBlockReward, m_blockReward); } - TEST_F(block_reward_and_last_block_sizes, handles_block_size_gt_median) - { - do_test(m_last_block_sizes_median + 1); - ASSERT_TRUE(m_block_not_too_big); - ASSERT_LT(m_block_reward, m_standard_block_reward); + TEST_F(getBlockReward_and_currentBlockSize, handles_block_size_eq_median) { + do_test(testMedian); + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(m_standardBlockReward, m_blockReward); } - TEST_F(block_reward_and_last_block_sizes, handles_block_size_less_2_medians) - { - do_test(2 * m_last_block_sizes_median - 1); - ASSERT_TRUE(m_block_not_too_big); - ASSERT_LT(m_block_reward, m_standard_block_reward); - ASSERT_LT(0, m_block_reward); + TEST_F(getBlockReward_and_currentBlockSize, handles_block_size_gt_median) { + do_test(testMedian + 1); + ASSERT_FALSE(m_blockTooBig); + ASSERT_LT(m_blockReward, m_standardBlockReward); } - TEST_F(block_reward_and_last_block_sizes, handles_block_size_eq_2_medians) - { - do_test(2 * m_last_block_sizes_median); - ASSERT_TRUE(m_block_not_too_big); - ASSERT_EQ(0, m_block_reward); + TEST_F(getBlockReward_and_currentBlockSize, handles_block_size_less_2_medians) { + do_test(2 * testMedian - 1); + ASSERT_FALSE(m_blockTooBig); + ASSERT_LT(m_blockReward, m_standardBlockReward); + ASSERT_GT(m_blockReward, 0); } - TEST_F(block_reward_and_last_block_sizes, handles_block_size_gt_2_medians) - { - do_test(2 * m_last_block_sizes_median + 1); - ASSERT_FALSE(m_block_not_too_big); + TEST_F(getBlockReward_and_currentBlockSize, handles_block_size_eq_2_medians) { + do_test(2 * testMedian); + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(0, m_blockReward); } - TEST_F(block_reward_and_last_block_sizes, calculates_correctly) - { - ASSERT_EQ(0, m_last_block_sizes_median % 8); + TEST_F(getBlockReward_and_currentBlockSize, handles_block_size_gt_2_medians) { + do_test(2 * testMedian + 1); + ASSERT_TRUE(m_blockTooBig); + } - do_test(m_last_block_sizes_median * 9 / 8); - ASSERT_TRUE(m_block_not_too_big); - ASSERT_EQ(m_block_reward, m_standard_block_reward * 63 / 64); + TEST_F(getBlockReward_and_currentBlockSize, calculates_correctly) { + ASSERT_EQ(0, testMedian % 8); + + // reward = 1 - (k - 1)^2 + // k = 9/8 => reward = 63/64 + do_test(testMedian * 9 / 8); + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(m_standardBlockReward * 63 / 64, m_blockReward); // 3/2 = 12/8 - do_test(m_last_block_sizes_median * 3 / 2); - ASSERT_TRUE(m_block_not_too_big); - ASSERT_EQ(m_block_reward, m_standard_block_reward * 3 / 4); + do_test(testMedian * 3 / 2); + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(m_standardBlockReward * 3 / 4, m_blockReward); - do_test(m_last_block_sizes_median * 15 / 8); - ASSERT_TRUE(m_block_not_too_big); - ASSERT_EQ(m_block_reward, m_standard_block_reward * 15 / 64); + do_test(testMedian * 15 / 8); + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(m_standardBlockReward * 15 / 64, m_blockReward); + } + //-------------------------------------------------------------------------------------------------------------------- + const unsigned int testEmissionSpeedFactor = 4; + const size_t testGrantedFullRewardZone = 1000; + const size_t testMedian = testGrantedFullRewardZone; + const size_t testBlockSize = testMedian + testMedian * 8 / 10; // expected penalty 0.64 * reward + const uint64_t testPenalty = 64; // percentage + const uint64_t testMoneySupply = UINT64_C(1000000000); + const uint64_t expectedBaseReward = 62500000; // testMoneySupply >> testEmissionSpeedFactor + const uint64_t expectedBlockReward = 22500000; // expectedBaseReward - expectedBaseReward * testPenalty / 100 + //-------------------------------------------------------------------------------------------------------------------- + class getBlockReward_fee_and_penalizeFee_test : public ::testing::Test { + public: + getBlockReward_fee_and_penalizeFee_test() : + ::testing::Test(), + m_currency(cryptonote::CurrencyBuilder(). + blockGrantedFullRewardZone(testGrantedFullRewardZone). + moneySupply(testMoneySupply). + emissionSpeedFactor(testEmissionSpeedFactor). + currency()) { + } + + protected: + virtual void SetUp() { + uint64_t blockReward; + int64_t emissionChange; + + m_blockTooBig = !m_currency.getBlockReward(testMedian, testBlockSize, 0, 0, false, blockReward, emissionChange); + + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(expectedBlockReward, blockReward); + ASSERT_EQ(expectedBlockReward, emissionChange); + } + + void do_test(uint64_t alreadyGeneratedCoins, uint64_t fee, bool penalizeFee) { + m_blockTooBig = !m_currency.getBlockReward(testMedian, testBlockSize, alreadyGeneratedCoins, fee, penalizeFee, + m_blockReward, m_emissionChange); + } + + cryptonote::Currency m_currency; + bool m_blockTooBig; + int64_t m_emissionChange; + uint64_t m_blockReward; + }; + + TEST_F(getBlockReward_fee_and_penalizeFee_test, handles_zero_fee_and_no_penalize_fee) { + do_test(0, 0, false); + + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(expectedBlockReward, m_blockReward); + ASSERT_EQ(expectedBlockReward, m_emissionChange); + ASSERT_GT(m_emissionChange, 0); + } + + TEST_F(getBlockReward_fee_and_penalizeFee_test, handles_zero_fee_and_penalize_fee) { + do_test(0, 0, true); + + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(expectedBlockReward, m_blockReward); + ASSERT_EQ(expectedBlockReward, m_emissionChange); + ASSERT_GT(m_emissionChange, 0); + } + + TEST_F(getBlockReward_fee_and_penalizeFee_test, handles_fee_lt_block_reward_and_no_penalize_fee) { + uint64_t fee = expectedBlockReward / 2; + do_test(0, fee, false); + + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(expectedBlockReward + fee, m_blockReward); + ASSERT_EQ(expectedBlockReward, m_emissionChange); + ASSERT_GT(m_emissionChange, 0); + } + + TEST_F(getBlockReward_fee_and_penalizeFee_test, handles_fee_lt_block_reward_and_penalize_fee) { + uint64_t fee = expectedBlockReward / 2; + do_test(0, fee, true); + + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(expectedBlockReward + fee - fee * testPenalty / 100, m_blockReward); + ASSERT_EQ(expectedBlockReward - fee * testPenalty / 100, m_emissionChange); + ASSERT_GT(m_emissionChange, 0); + } + + TEST_F(getBlockReward_fee_and_penalizeFee_test, handles_fee_eq_block_reward_and_no_penalize_fee) { + uint64_t fee = expectedBlockReward; + do_test(0, fee, false); + + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(expectedBlockReward + fee, m_blockReward); + ASSERT_EQ(expectedBlockReward, m_emissionChange); + ASSERT_GT(m_emissionChange, 0); + } + + TEST_F(getBlockReward_fee_and_penalizeFee_test, handles_fee_eq_block_reward_and_penalize_fee) { + uint64_t fee = expectedBlockReward; + do_test(0, fee, true); + + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(expectedBlockReward + fee - fee * testPenalty / 100, m_blockReward); + ASSERT_EQ(expectedBlockReward - fee * testPenalty / 100, m_emissionChange); + ASSERT_GT(m_emissionChange, 0); + } + + TEST_F(getBlockReward_fee_and_penalizeFee_test, handles_fee_gt_block_reward_and_no_penalize_fee) { + uint64_t fee = 2 * expectedBlockReward; + do_test(0, fee, false); + + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(expectedBlockReward + fee, m_blockReward); + ASSERT_EQ(expectedBlockReward, m_emissionChange); + } + + TEST_F(getBlockReward_fee_and_penalizeFee_test, handles_fee_gt_block_reward_and_penalize_fee) { + uint64_t fee = 2 * expectedBlockReward; + do_test(0, fee, true); + + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(expectedBlockReward + fee - fee * testPenalty / 100, m_blockReward); + ASSERT_EQ(expectedBlockReward - fee * testPenalty / 100, m_emissionChange); + ASSERT_LT(m_emissionChange, 0); + } + + TEST_F(getBlockReward_fee_and_penalizeFee_test, handles_emission_change_eq_zero) { + uint64_t fee = expectedBlockReward * 100 / testPenalty; + do_test(0, fee, true); + + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(expectedBlockReward + fee - fee * testPenalty / 100, m_blockReward); + ASSERT_EQ(0, m_emissionChange); + } + + TEST_F(getBlockReward_fee_and_penalizeFee_test, handles_fee_if_block_reward_is_zero_and_no_penalize_fee) { + uint64_t fee = UINT64_C(100); + do_test(m_currency.moneySupply(), fee, false); + + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(fee, m_blockReward); + ASSERT_EQ(0, m_emissionChange); + } + + TEST_F(getBlockReward_fee_and_penalizeFee_test, handles_fee_if_block_reward_is_zero_and_penalize_fee) { + uint64_t fee = UINT64_C(100); + do_test(m_currency.moneySupply(), fee, true); + + ASSERT_FALSE(m_blockTooBig); + ASSERT_EQ(fee - fee * testPenalty / 100, m_blockReward); + ASSERT_EQ(-static_cast(fee * testPenalty / 100), m_emissionChange); } } diff --git a/tests/unit_tests/get_xtype_from_string.cpp b/tests/unit_tests/get_xtype_from_string.cpp index a2c345bf..28d0c5da 100644 --- a/tests/unit_tests/get_xtype_from_string.cpp +++ b/tests/unit_tests/get_xtype_from_string.cpp @@ -17,6 +17,9 @@ #include "gtest/gtest.h" +#include + +// epee #include using namespace epee::string_tools; diff --git a/tests/unit_tests/main.cpp b/tests/unit_tests/main.cpp index c6865d04..28ac4ab3 100644 --- a/tests/unit_tests/main.cpp +++ b/tests/unit_tests/main.cpp @@ -23,6 +23,10 @@ int main(int argc, char** argv) { epee::debug::get_set_enable_assert(true, false); + //set up logging options + epee::log_space::get_set_log_detalisation_level(true, LOG_LEVEL_0); + epee::log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL); + ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } diff --git a/tests/unit_tests/parse_amount.cpp b/tests/unit_tests/parse_amount.cpp index 6b900511..e1516b61 100644 --- a/tests/unit_tests/parse_amount.cpp +++ b/tests/unit_tests/parse_amount.cpp @@ -18,28 +18,33 @@ #include "gtest/gtest.h" #include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_core/Currency.h" using namespace cryptonote; namespace { + const size_t TEST_NUMBER_OF_DECIMAL_PLACES = 8; + void do_pos_test(uint64_t expected, const std::string& str) { + cryptonote::Currency currency = cryptonote::CurrencyBuilder().numberOfDecimalPlaces(TEST_NUMBER_OF_DECIMAL_PLACES).currency(); uint64_t val; std::string number_str = str; std::replace(number_str.begin(), number_str.end(), '_', '.'); number_str.erase(std::remove(number_str.begin(), number_str.end(), '~'), number_str.end()); - ASSERT_TRUE(parse_amount(val, number_str)); + ASSERT_TRUE(currency.parseAmount(number_str, val)); ASSERT_EQ(expected, val); } void do_neg_test(const std::string& str) { + cryptonote::Currency currency = cryptonote::CurrencyBuilder().numberOfDecimalPlaces(TEST_NUMBER_OF_DECIMAL_PLACES).currency(); uint64_t val; std::string number_str = str; std::replace(number_str.begin(), number_str.end(), '_', '.'); number_str.erase(std::remove(number_str.begin(), number_str.end(), '~'), number_str.end()); - ASSERT_FALSE(parse_amount(val, number_str)); + ASSERT_FALSE(currency.parseAmount(number_str, val)); } } diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index 8f1f0580..bc239a85 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -290,12 +290,12 @@ TEST(Serialization, serializes_transacion_signatures_correctly) { using namespace cryptonote; - transaction tx; - transaction tx1; + Transaction tx; + Transaction tx1; string blob; // Empty tx - tx.set_null(); + tx.clear(); ASSERT_TRUE(serialization::dump_binary(tx, blob)); ASSERT_EQ(5, blob.size()); // 5 bytes + 0 bytes extra + 0 bytes signatures ASSERT_TRUE(serialization::parse_binary(blob, tx1)); @@ -303,9 +303,9 @@ TEST(Serialization, serializes_transacion_signatures_correctly) ASSERT_EQ(linearize_vector2(tx.signatures), linearize_vector2(tx1.signatures)); // Miner tx without signatures - txin_gen txin_gen1; + TransactionInputGenerate txin_gen1; txin_gen1.height = 0; - tx.set_null(); + tx.clear(); tx.vin.push_back(txin_gen1); ASSERT_TRUE(serialization::dump_binary(tx, blob)); ASSERT_EQ(7, blob.size()); // 5 bytes + 2 bytes vin[0] + 0 bytes extra + 0 bytes signatures @@ -336,7 +336,7 @@ TEST(Serialization, serializes_transacion_signatures_correctly) tx.signatures[1].resize(1); ASSERT_FALSE(serialization::dump_binary(tx, blob)); - // Two txin_gen, no signatures + // Two TransactionInputGenerate, no signatures tx.vin.push_back(txin_gen1); tx.signatures.resize(0); ASSERT_TRUE(serialization::dump_binary(tx, blob)); @@ -345,11 +345,11 @@ TEST(Serialization, serializes_transacion_signatures_correctly) ASSERT_EQ(tx, tx1); ASSERT_EQ(linearize_vector2(tx.signatures), linearize_vector2(tx1.signatures)); - // Two txin_gen, signatures vector contains only one empty element + // Two TransactionInputGenerate, signatures vector contains only one empty element tx.signatures.resize(1); ASSERT_FALSE(serialization::dump_binary(tx, blob)); - // Two txin_gen, signatures vector contains two empty elements + // Two TransactionInputGenerate, signatures vector contains two empty elements tx.signatures.resize(2); ASSERT_TRUE(serialization::dump_binary(tx, blob)); ASSERT_EQ(9, blob.size()); // 5 bytes + 2 * 2 bytes vins + 0 bytes extra + 0 bytes signatures @@ -357,11 +357,11 @@ TEST(Serialization, serializes_transacion_signatures_correctly) ASSERT_EQ(tx, tx1); ASSERT_EQ(linearize_vector2(tx.signatures), linearize_vector2(tx1.signatures)); - // Two txin_gen, signatures vector contains three empty elements + // Two TransactionInputGenerate, signatures vector contains three empty elements tx.signatures.resize(3); ASSERT_FALSE(serialization::dump_binary(tx, blob)); - // Two txin_gen, signatures vector contains two non empty elements + // Two TransactionInputGenerate, signatures vector contains two non empty elements tx.signatures.resize(2); tx.signatures[0].resize(1); tx.signatures[1].resize(1); @@ -380,8 +380,8 @@ TEST(Serialization, serializes_transacion_signatures_correctly) ASSERT_FALSE(serialization::parse_binary(blob, tx1)); // Not enough signature vectors for all inputs - txin_to_key txin_to_key1; - txin_to_key1.key_offsets.resize(2); + TransactionInputToKey txin_to_key1; + txin_to_key1.keyOffsets.resize(2); tx.vin.clear(); tx.vin.push_back(txin_to_key1); tx.vin.push_back(txin_to_key1); diff --git a/tests/unit_tests/test_format_utils.cpp b/tests/unit_tests/test_format_utils.cpp index 37c37ca4..f00e1530 100644 --- a/tests/unit_tests/test_format_utils.cpp +++ b/tests/unit_tests/test_format_utils.cpp @@ -19,8 +19,13 @@ #include +// epee +#include "misc_language.h" + #include "common/util.h" +#include "cryptonote_core/account.h" #include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_core/Currency.h" TEST(parse_tx_extra, handles_empty_extra) @@ -118,25 +123,27 @@ TEST(parse_tx_extra, handles_pub_key_and_padding) TEST(parse_and_validate_tx_extra, is_valid_tx_extra_parsed) { - cryptonote::transaction tx = AUTO_VAL_INIT(tx); + cryptonote::Currency currency = cryptonote::CurrencyBuilder().currency(); + cryptonote::Transaction tx = AUTO_VAL_INIT(tx); cryptonote::account_base acc; acc.generate(); cryptonote::blobdata b = "dsdsdfsdfsf"; - ASSERT_TRUE(cryptonote::construct_miner_tx(0, 0, 10000000000000, 1000, MINIMUM_FEE, acc.get_keys().m_account_address, tx, b, 1)); + ASSERT_TRUE(currency.constructMinerTx(0, 0, 10000000000000, 1000, currency.minimumFee(), acc.get_keys().m_account_address, tx, b, 1)); crypto::public_key tx_pub_key = cryptonote::get_tx_pub_key_from_extra(tx); ASSERT_NE(tx_pub_key, cryptonote::null_pkey); } TEST(parse_and_validate_tx_extra, fails_on_big_extra_nonce) { - cryptonote::transaction tx = AUTO_VAL_INIT(tx); + cryptonote::Currency currency = cryptonote::CurrencyBuilder().currency(); + cryptonote::Transaction tx = AUTO_VAL_INIT(tx); cryptonote::account_base acc; acc.generate(); cryptonote::blobdata b(TX_EXTRA_NONCE_MAX_COUNT + 1, 0); - ASSERT_FALSE(cryptonote::construct_miner_tx(0, 0, 10000000000000, 1000, MINIMUM_FEE, acc.get_keys().m_account_address, tx, b, 1)); + ASSERT_FALSE(currency.constructMinerTx(0, 0, 10000000000000, 1000, currency.minimumFee(), acc.get_keys().m_account_address, tx, b, 1)); } TEST(parse_and_validate_tx_extra, fails_on_wrong_size_in_extra_nonce) { - cryptonote::transaction tx = AUTO_VAL_INIT(tx); + cryptonote::Transaction tx = AUTO_VAL_INIT(tx); tx.extra.resize(20, 0); tx.extra[0] = TX_EXTRA_NONCE; tx.extra[1] = 255; @@ -145,44 +152,45 @@ TEST(parse_and_validate_tx_extra, fails_on_wrong_size_in_extra_nonce) } TEST(validate_parse_amount_case, validate_parse_amount) { + cryptonote::Currency currency = cryptonote::CurrencyBuilder().numberOfDecimalPlaces(8).currency(); uint64_t res = 0; - bool r = cryptonote::parse_amount(res, "0.0001"); + bool r = currency.parseAmount("0.0001", res); ASSERT_TRUE(r); ASSERT_EQ(res, 10000); - r = cryptonote::parse_amount(res, "100.0001"); + r = currency.parseAmount("100.0001", res); ASSERT_TRUE(r); ASSERT_EQ(res, 10000010000); - r = cryptonote::parse_amount(res, "000.0000"); + r = currency.parseAmount("000.0000", res); ASSERT_TRUE(r); ASSERT_EQ(res, 0); - r = cryptonote::parse_amount(res, "0"); + r = currency.parseAmount("0", res); ASSERT_TRUE(r); ASSERT_EQ(res, 0); - r = cryptonote::parse_amount(res, " 100.0001 "); + r = currency.parseAmount(" 100.0001 ", res); ASSERT_TRUE(r); ASSERT_EQ(res, 10000010000); - r = cryptonote::parse_amount(res, " 100.0000 "); + r = currency.parseAmount(" 100.0000 ", res); ASSERT_TRUE(r); ASSERT_EQ(res, 10000000000); - r = cryptonote::parse_amount(res, " 100. 0000 "); + r = currency.parseAmount(" 100. 0000 ", res); ASSERT_FALSE(r); - r = cryptonote::parse_amount(res, "100. 0000"); + r = currency.parseAmount("100. 0000", res); ASSERT_FALSE(r); - r = cryptonote::parse_amount(res, "100 . 0000"); + r = currency.parseAmount("100 . 0000", res); ASSERT_FALSE(r); - r = cryptonote::parse_amount(res, "100.00 00"); + r = currency.parseAmount("100.00 00", res); ASSERT_FALSE(r); - r = cryptonote::parse_amount(res, "1 00.00 00"); + r = currency.parseAmount("1 00.00 00", res); ASSERT_FALSE(r); } diff --git a/tests/unit_tests/test_protocol_pack.cpp b/tests/unit_tests/test_protocol_pack.cpp index c5c7a9f2..7ca59dcf 100644 --- a/tests/unit_tests/test_protocol_pack.cpp +++ b/tests/unit_tests/test_protocol_pack.cpp @@ -15,6 +15,8 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . +#include + #include "gtest/gtest.h" #include "include_base_utils.h" diff --git a/tests/unit_tests/test_wallet.cpp b/tests/unit_tests/test_wallet.cpp index 48efd03c..f64f8315 100644 --- a/tests/unit_tests/test_wallet.cpp +++ b/tests/unit_tests/test_wallet.cpp @@ -24,6 +24,7 @@ #include "INode.h" #include "wallet/Wallet.h" #include "cryptonote_core/account.h" +#include "cryptonote_core/Currency.h" #include "INodeStubs.h" #include "TestBlockchainGenerator.h" @@ -115,6 +116,18 @@ public: std::promise loadPromise; }; +struct SaveOnInitWalletObserver: public CryptoNote::IWalletObserver { + SaveOnInitWalletObserver(CryptoNote::Wallet* wallet) : wallet(wallet) {}; + virtual ~SaveOnInitWalletObserver() {} + + virtual void initCompleted(std::error_code result) { + wallet->save(stream, true, true); + } + + CryptoNote::Wallet* wallet; + std::stringstream stream; +}; + static const uint64_t TEST_BLOCK_REWARD = 70368744177663; CryptoNote::TransactionId TransferMoney(CryptoNote::Wallet& from, CryptoNote::Wallet& to, int64_t amount, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "") { @@ -152,6 +165,9 @@ void WaitWalletSave(TrivialWalletObserver* observer) { class WalletApi : public ::testing::Test { public: + WalletApi() : m_currency(cryptonote::CurrencyBuilder().currency()), generator(m_currency) { + } + void SetUp(); protected: @@ -164,6 +180,8 @@ protected: void TestSendMoney(int64_t transferAmount, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = ""); void performTransferWithErrorTx(const std::array& amounts, uint64_t fee); + cryptonote::Currency m_currency; + TestBlockchainGenerator generator; std::shared_ptr aliceWalletObserver; @@ -188,7 +206,7 @@ void WalletApi::prepareAliceWallet() { aliceNode.reset(new INodeTrivialRefreshStub(generator)); aliceWalletObserver.reset(new TrivialWalletObserver()); - alice.reset(new CryptoNote::Wallet(*aliceNode)); + alice.reset(new CryptoNote::Wallet(m_currency, *aliceNode)); alice->addObserver(aliceWalletObserver.get()); } @@ -196,7 +214,7 @@ void WalletApi::prepareBobWallet() { bobNode.reset(new INodeTrivialRefreshStub(generator)); bobWalletObserver.reset(new TrivialWalletObserver()); - bob.reset(new CryptoNote::Wallet(*bobNode)); + bob.reset(new CryptoNote::Wallet(m_currency, *bobNode)); bob->addObserver(bobWalletObserver.get()); } @@ -204,13 +222,13 @@ void WalletApi::prepareCarolWallet() { carolNode.reset(new INodeTrivialRefreshStub(generator)); carolWalletObserver.reset(new TrivialWalletObserver()); - carol.reset(new CryptoNote::Wallet(*carolNode)); + carol.reset(new CryptoNote::Wallet(m_currency, *carolNode)); carol->addObserver(carolWalletObserver.get()); } void WalletApi::GetOneBlockReward(CryptoNote::Wallet& wallet) { - cryptonote::account_public_address address; - ASSERT_TRUE(cryptonote::get_account_address_from_str(address, wallet.getAddress())); + cryptonote::AccountPublicAddress address; + ASSERT_TRUE(m_currency.parseAccountAddressString(wallet.getAddress(), address)); generator.getBlockRewardForAddress(address); } @@ -301,6 +319,14 @@ void WaitWalletLoad(TrivialWalletObserver* observer, std::error_code& ec) { ASSERT_TRUE(observer->waitForLoadEnd(ec)); } +TEST_F(WalletApi, initAndSave) { + SaveOnInitWalletObserver saveOnInit(alice.get()); + alice->addObserver(&saveOnInit); + alice->initAndGenerate("pass"); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + alice->shutdown(); +} + TEST_F(WalletApi, refreshWithMoney) { alice->initAndGenerate("pass"); @@ -309,8 +335,8 @@ TEST_F(WalletApi, refreshWithMoney) { ASSERT_EQ(alice->actualBalance(), 0); ASSERT_EQ(alice->pendingBalance(), 0); - cryptonote::account_public_address address; - ASSERT_TRUE(cryptonote::get_account_address_from_str(address, alice->getAddress())); + cryptonote::AccountPublicAddress address; + ASSERT_TRUE(m_currency.parseAccountAddressString(alice->getAddress(), address)); generator.getBlockRewardForAddress(address); alice->startRefresh(); @@ -323,6 +349,34 @@ TEST_F(WalletApi, refreshWithMoney) { alice->shutdown(); } +TEST_F(WalletApi, initWithMoney) { + std::stringstream archive; + + alice->initAndGenerate("pass"); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + alice->save(archive, true, true); + ASSERT_NO_FATAL_FAILURE(WaitWalletSave(aliceWalletObserver.get())); + + ASSERT_EQ(alice->actualBalance(), 0); + ASSERT_EQ(alice->pendingBalance(), 0); + + cryptonote::AccountPublicAddress address; + ASSERT_TRUE(m_currency.parseAccountAddressString(alice->getAddress(), address)); + + alice->shutdown(); + + generator.getBlockRewardForAddress(address); + + prepareAliceWallet(); + alice->initAndLoad(archive, "pass"); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + EXPECT_EQ(alice->actualBalance(), 0); + EXPECT_EQ(alice->pendingBalance(), TEST_BLOCK_REWARD); + + alice->shutdown(); +} + TEST_F(WalletApi, TransactionsAndTransfersAfterSend) { prepareBobWallet(); prepareCarolWallet(); @@ -365,7 +419,7 @@ TEST_F(WalletApi, TransactionsAndTransfersAfterSend) { EXPECT_EQ(alice->getTransactionCount(), 5); - CryptoNote::Transaction tx; + CryptoNote::TransactionInfo tx; //Transaction with id = 0 is tested in getTransactionSuccess ASSERT_TRUE(alice->getTransaction(1, tx)); @@ -482,7 +536,7 @@ TEST_F(WalletApi, saveAndLoadCacheDetails) { ASSERT_EQ(alice->getTransactionCount(), 3); ASSERT_EQ(alice->getTransferCount(), 3); - CryptoNote::Transaction tx; + CryptoNote::TransactionInfo tx; ASSERT_TRUE(alice->getTransaction(1, tx)); EXPECT_EQ(tx.totalAmount, amount1 + amount2 + fee); EXPECT_EQ(tx.fee, fee); @@ -533,7 +587,7 @@ TEST_F(WalletApi, getTransactionSuccess) { alice->startRefresh(); ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); - CryptoNote::Transaction tx; + CryptoNote::TransactionInfo tx; ASSERT_EQ(alice->getTransactionCount(), 1); ASSERT_TRUE(alice->getTransaction(0, tx)); @@ -552,7 +606,7 @@ TEST_F(WalletApi, getTransactionFailure) { ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); - CryptoNote::Transaction tx; + CryptoNote::TransactionInfo tx; ASSERT_EQ(alice->getTransactionCount(), 0); ASSERT_FALSE(alice->getTransaction(0, tx)); @@ -572,7 +626,7 @@ TEST_F(WalletApi, useNotInitializedObject) { EXPECT_THROW(alice->findTransactionByTransferId(1), std::system_error); - CryptoNote::Transaction tx; + CryptoNote::TransactionInfo tx; CryptoNote::Transfer tr; EXPECT_THROW(alice->getTransaction(1, tx), std::system_error); EXPECT_THROW(alice->getTransfer(2, tr), std::system_error); @@ -685,7 +739,7 @@ TEST_F(WalletApi, saveAndLoadErroneousTxsCacheDetails) { EXPECT_EQ(alice->getTransactionCount(), 2); EXPECT_EQ(alice->getTransferCount(), 2); - CryptoNote::Transaction tx; + CryptoNote::TransactionInfo tx; ASSERT_TRUE(alice->getTransaction(1, tx)); EXPECT_EQ(tx.totalAmount, amounts[3] + amounts[4] + fee); EXPECT_EQ(tx.firstTransferId, 0); @@ -747,7 +801,7 @@ TEST_F(WalletApi, saveAndLoadErroneousTxsCacheNoDetails) { EXPECT_EQ(alice->getTransactionCount(), 2); EXPECT_EQ(alice->getTransferCount(), 0); - CryptoNote::Transaction tx; + CryptoNote::TransactionInfo tx; ASSERT_TRUE(alice->getTransaction(1, tx)); EXPECT_EQ(tx.totalAmount, amounts[3] + amounts[4] + fee); EXPECT_EQ(tx.firstTransferId, CryptoNote::INVALID_TRANSFER_ID); diff --git a/tests/unit_tests/tx_pool.cpp b/tests/unit_tests/tx_pool.cpp new file mode 100644 index 00000000..d506492d --- /dev/null +++ b/tests/unit_tests/tx_pool.cpp @@ -0,0 +1,397 @@ +// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "gtest/gtest.h" + +#include + +#include "cryptonote_core/account.h" +#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_core/Currency.h" +#include "cryptonote_core/tx_pool.h" + +using namespace cryptonote; +using namespace CryptoNote; + +class TransactionValidator : public CryptoNote::ITransactionValidator { + virtual bool checkTransactionInputs(const cryptonote::Transaction& tx, BlockInfo& maxUsedBlock) { + return true; + } + + virtual bool checkTransactionInputs(const cryptonote::Transaction& tx, BlockInfo& maxUsedBlock, BlockInfo& lastFailed) { + return true; + } + + virtual bool haveSpentKeyImages(const cryptonote::Transaction& tx) { + return false; + } +}; + +class FakeTimeProvider : public ITimeProvider { +public: + FakeTimeProvider(time_t currentTime = time(nullptr)) + : timeNow(currentTime) {} + + time_t timeNow; + virtual time_t now() { return timeNow; } +}; + + +class TestTransactionGenerator { + +public: + + TestTransactionGenerator(const cryptonote::Currency& currency, size_t ringSize) : + m_currency(currency), + m_ringSize(ringSize), + m_miners(ringSize), + m_miner_txs(ringSize), + m_public_keys(ringSize), + m_public_key_ptrs(ringSize) + { + rv_acc.generate(); + } + + bool createSources() { + + size_t real_source_idx = m_ringSize / 2; + + std::vector output_entries; + for (size_t i = 0; i < m_ringSize; ++i) + { + m_miners[i].generate(); + + if (!m_currency.constructMinerTx(0, 0, 0, 2, 0, m_miners[i].get_keys().m_account_address, m_miner_txs[i])) { + return false; + } + + TransactionOutputToKey tx_out = boost::get(m_miner_txs[i].vout[0].target); + output_entries.push_back(std::make_pair(i, tx_out.key)); + m_public_keys[i] = tx_out.key; + m_public_key_ptrs[i] = &m_public_keys[i]; + } + + m_source_amount = m_miner_txs[0].vout[0].amount; + + tx_source_entry source_entry; + source_entry.amount = m_source_amount; + source_entry.real_out_tx_key = get_tx_pub_key_from_extra(m_miner_txs[real_source_idx]); + source_entry.real_output_in_tx_index = 0; + source_entry.outputs.swap(output_entries); + source_entry.real_output = real_source_idx; + + m_sources.push_back(source_entry); + + m_realSenderKeys = m_miners[real_source_idx].get_keys(); + + return true; + } + + void construct(uint64_t amount, uint64_t fee, size_t outputs, Transaction& tx) { + + std::vector destinations; + uint64_t amountPerOut = (amount - fee) / outputs; + + for (size_t i = 0; i < outputs; ++i) { + destinations.push_back(tx_destination_entry(amountPerOut, rv_acc.get_keys().m_account_address)); + } + + construct_tx(m_realSenderKeys, m_sources, destinations, std::vector(), tx, 0); + } + + std::vector m_miners; + std::vector m_miner_txs; + std::vector m_sources; + std::vector m_public_keys; + std::vector m_public_key_ptrs; + + const cryptonote::Currency& m_currency; + const size_t m_ringSize; + account_keys m_realSenderKeys; + uint64_t m_source_amount; + account_base rv_acc; +}; + + + +namespace +{ + static const size_t textMaxCumulativeSize = std::numeric_limits::max(); + + void GenerateTransaction(const cryptonote::Currency& currency, Transaction& tx, uint64_t fee, size_t outputs) { + TestTransactionGenerator txGenerator(currency, 1); + txGenerator.createSources(); + txGenerator.construct(txGenerator.m_source_amount, fee, outputs, tx); + } + + template + class TestPool : public tx_memory_pool { + public: + + Validator validator; + TimeProvider timeProvider; + + TestPool(const cryptonote::Currency& m_currency) : + tx_memory_pool(m_currency, validator, timeProvider) {} + }; + + class TxTestBase { + public: + TxTestBase(size_t ringSize) : + m_currency(cryptonote::CurrencyBuilder().currency()), + txGenerator(m_currency, ringSize), + pool(m_currency, validator, m_time) + { + txGenerator.createSources(); + } + + void construct(uint64_t fee, size_t outputs, Transaction& tx) { + txGenerator.construct(txGenerator.m_source_amount, fee, outputs, tx); + } + + cryptonote::Currency m_currency; + CryptoNote::RealTimeProvider m_time; + TestTransactionGenerator txGenerator; + TransactionValidator validator; + tx_memory_pool pool; + }; + + void InitBlock(Block& bl, uint8_t majorVersion = BLOCK_MAJOR_VERSION_1) { + bl.majorVersion = majorVersion; + bl.minorVersion = 0; + bl.nonce = 0; + bl.timestamp = time(0); + bl.prevId = null_hash; + } + +} + +TEST(tx_pool, add_one_tx) +{ + TxTestBase test(1); + Transaction tx; + + test.construct(test.m_currency.minimumFee(), 1, tx); + + tx_verification_context tvc = boost::value_initialized(); + + ASSERT_TRUE(test.pool.add_tx(tx, tvc, false)); + ASSERT_FALSE(tvc.m_verifivation_failed); +}; + +TEST(tx_pool, take_tx) +{ + TxTestBase test(1); + Transaction tx; + + test.construct(test.m_currency.minimumFee(), 1, tx); + + auto txhash = get_transaction_hash(tx); + + tx_verification_context tvc = boost::value_initialized(); + + ASSERT_TRUE(test.pool.add_tx(tx, tvc, false)); + ASSERT_FALSE(tvc.m_verifivation_failed); + + Transaction txOut; + size_t blobSize; + uint64_t fee = 0; + + ASSERT_TRUE(test.pool.take_tx(txhash, txOut, blobSize, fee)); + ASSERT_EQ(fee, test.m_currency.minimumFee()); + ASSERT_EQ(tx, txOut); +}; + + +TEST(tx_pool, double_spend_tx) +{ + TxTestBase test(1); + Transaction tx, tx_double; + + test.construct(test.m_currency.minimumFee(), 1, tx); + + tx_verification_context tvc = boost::value_initialized(); + + ASSERT_TRUE(test.pool.add_tx(tx, tvc, false)); + ASSERT_FALSE(tvc.m_verifivation_failed); + + test.txGenerator.rv_acc.generate(); // generate new receiver address + test.construct(test.m_currency.minimumFee(), 1, tx_double); + + ASSERT_FALSE(test.pool.add_tx(tx_double, tvc, false)); + ASSERT_TRUE(tvc.m_verifivation_failed); +} + + +TEST(tx_pool, fillblock_same_fee) +{ + cryptonote::Currency currency = cryptonote::CurrencyBuilder().currency(); + TestPool pool(currency); + uint64_t fee = currency.minimumFee(); + + std::unordered_map> transactions; + + // generate transactions + for (int i = 1; i <= 50; ++i) { + TestTransactionGenerator txGenerator(currency, 1); + txGenerator.createSources(); + + std::unique_ptr txptr(new Transaction); + Transaction& tx = *txptr; + + txGenerator.construct(txGenerator.m_source_amount, fee, i, tx); + + tx_verification_context tvc = boost::value_initialized(); + ASSERT_TRUE(pool.add_tx(tx, tvc, false)); + ASSERT_TRUE(tvc.m_added_to_pool); + + transactions[get_transaction_hash(tx)] = std::move(txptr); + } + + Block bl; + + InitBlock(bl); + + size_t totalSize = 0; + uint64_t txFee = 0; + uint64_t median = 5000; + + ASSERT_TRUE(pool.fill_block_template(bl, median, textMaxCumulativeSize, 0, totalSize, txFee)); + ASSERT_TRUE(totalSize*100 < median*125); + + // now, check that the block is opimally filled + // if fee is fixed, transactions with smaller number of outputs should be included + + size_t maxOuts = 0; + + for (auto& th : bl.txHashes) { + auto iter = transactions.find(th); + ASSERT_TRUE(iter != transactions.end()); + + size_t txouts = iter->second->vout.size(); + + if (txouts > maxOuts) + maxOuts = txouts; + } + + ASSERT_TRUE(maxOuts <= bl.txHashes.size()); +} + + +TEST(tx_pool, fillblock_same_size) +{ + cryptonote::Currency currency = cryptonote::CurrencyBuilder().currency(); + TestPool pool(currency); + + const uint64_t fee = currency.minimumFee(); + const size_t totalTransactions = 50; + + std::unordered_map> transactions; + + + // generate transactions + for (int i = 0; i <= totalTransactions; ++i) { + + TestTransactionGenerator txGenerator(currency, 1); + txGenerator.createSources(); + + std::unique_ptr txptr(new Transaction); + Transaction& tx = *txptr; + + // interleave fee and fee*2 + txGenerator.construct(txGenerator.m_source_amount, fee + (fee * (i&1)), 1, tx); + + tx_verification_context tvc = boost::value_initialized(); + ASSERT_TRUE(pool.add_tx(tx, tvc, false)); + ASSERT_TRUE(tvc.m_added_to_pool); + + transactions[get_transaction_hash(tx)] = std::move(txptr); + } + + + Block bl; + + InitBlock(bl); + + size_t totalSize = 0; + uint64_t txFee = 0; + uint64_t median = 5000; + + ASSERT_TRUE(pool.fill_block_template(bl, median, textMaxCumulativeSize, 0, totalSize, txFee)); + ASSERT_TRUE(totalSize * 100 < median * 125); + + // check that fill_block_template prefers transactions with double fee + + size_t doubleFee = 0; + + for (auto& th : bl.txHashes) { + + auto iter = transactions.find(th); + ASSERT_TRUE(iter != transactions.end()); + + if (get_tx_fee(*iter->second) > fee) + ++doubleFee; + } + + ASSERT_TRUE(doubleFee == std::min(bl.txHashes.size(), totalTransactions / 2)); + +} + + +TEST(tx_pool, cleanup_stale_tx) +{ + cryptonote::Currency currency = cryptonote::CurrencyBuilder().currency(); + TestPool pool(currency); + const uint64_t fee = currency.minimumFee(); + + time_t startTime = pool.timeProvider.now(); + + for (int i = 0; i < 3; ++i) { + Transaction tx; + GenerateTransaction(currency, tx, fee, 1); + + tx_verification_context tvc = boost::value_initialized(); + ASSERT_TRUE(pool.add_tx(tx, tvc, false)); // main chain + ASSERT_TRUE(tvc.m_added_to_pool); + + pool.timeProvider.timeNow += 60 * 60 * 2; // add 2 hours + } + + for (int i = 0; i < 5; ++i) { + Transaction tx; + GenerateTransaction(currency, tx, fee, 1); + + tx_verification_context tvc = boost::value_initialized(); + ASSERT_TRUE(pool.add_tx(tx, tvc, true)); // alternative chain + ASSERT_TRUE(tvc.m_added_to_pool); + + pool.timeProvider.timeNow += 60 * 60 * 2; // add 2 hours + } + + + ASSERT_EQ(8, pool.get_transactions_count()); + + pool.timeProvider.timeNow = startTime + currency.mempoolTxLiveTime() + 3*60*60; + pool.on_idle(); // 2 transactions should be removed + + ASSERT_EQ(6, pool.get_transactions_count()); + + pool.timeProvider.timeNow = startTime + currency.mempoolTxFromAltBlockLiveTime() + (3*2+3) * 60 * 60; + pool.on_idle(); // all transactions from main chain and 2 transactions from altchain should be removed + + ASSERT_EQ(3, pool.get_transactions_count()); +}