From 69a770823ef788f9f989e7d1bc137178b9efaef9 Mon Sep 17 00:00:00 2001 From: Antonio Juarez Date: Sat, 21 Jun 2014 15:09:13 +0100 Subject: [PATCH 1/2] Fix bytecoin folder creation on startup --- src/cryptonote_core/blockchain_storage.cpp | 5 +++++ src/version.h.in | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/cryptonote_core/blockchain_storage.cpp b/src/cryptonote_core/blockchain_storage.cpp index f569e506..5fb23fa6 100644 --- a/src/cryptonote_core/blockchain_storage.cpp +++ b/src/cryptonote_core/blockchain_storage.cpp @@ -207,6 +207,11 @@ uint64_t blockchain_storage::get_current_blockchain_height() { bool blockchain_storage::init(const std::string& config_folder) { CRITICAL_REGION_LOCAL(m_blockchain_lock); + if (!tools::create_directories_if_necessary(config_folder)) { + LOG_ERROR("Failed to create data directory: " << m_config_folder); + return false; + } + m_config_folder = config_folder; LOG_PRINT_L0("Loading blockchain..."); if (!m_blocks.open(appendPath(config_folder, "blocks.dat"), appendPath(config_folder, "blockindexes.dat"), 1024)) { diff --git a/src/version.h.in b/src/version.h.in index e412253e..05270416 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.10" -#define PROJECT_VERSION_BUILD_NO "65" +#define PROJECT_VERSION_BUILD_NO "71" #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO "(" BUILD_COMMIT_ID ")" From 76bb193b0556aaaa3ffa5d48db7bd28eceb36300 Mon Sep 17 00:00:00 2001 From: Antonio Juarez Date: Wed, 25 Jun 2014 18:21:42 +0100 Subject: [PATCH 2/2] Minimum fix increase, high level api --- CMakeLists.txt | 4 +- ReleaseNotes.txt | 8 + include/INode.h | 45 +- include/IWallet.h | 8 +- src/CMakeLists.txt | 6 +- src/common/ObserverManager.h | 116 +++ src/common/base58.cpp | 2 +- src/crypto/chacha8.h | 12 +- src/crypto/hash-ops.h | 6 +- src/crypto/hash.h | 22 +- src/crypto/slow-hash.c | 362 ++++----- src/crypto/slow-hash.cpp | 60 ++ src/crypto/slow-hash.inl | 184 +++++ src/cryptonote_config.h | 5 +- src/cryptonote_core/blockchain_storage.cpp | 104 +-- src/cryptonote_core/blockchain_storage.h | 16 +- src/cryptonote_core/cryptonote_core.cpp | 89 +-- src/cryptonote_core/cryptonote_core.h | 8 +- .../cryptonote_format_utils.cpp | 12 +- src/cryptonote_core/cryptonote_format_utils.h | 4 +- src/cryptonote_core/miner.cpp | 21 +- src/cryptonote_core/miner.h | 12 +- src/cryptonote_core/tx_pool.cpp | 299 +++---- src/cryptonote_core/tx_pool.h | 113 +-- src/daemon/daemon.cpp | 2 +- src/miner/simpleminer.cpp | 5 +- src/node_rpc_proxy/InitState.h | 83 ++ src/node_rpc_proxy/NodeErrors.cpp | 13 + src/node_rpc_proxy/NodeErrors.h | 56 ++ src/node_rpc_proxy/NodeRpcProxy.cpp | 255 ++++++ src/node_rpc_proxy/NodeRpcProxy.h | 76 ++ src/rpc/core_rpc_server.cpp | 16 +- src/rpc/core_rpc_server_commands_defs.h | 10 +- src/simplewallet/simplewallet.cpp | 23 +- src/version.h.in | 4 +- src/wallet/Wallet.cpp | 491 ++++++++++++ src/wallet/Wallet.h | 124 +++ src/wallet/WalletAsyncContextCounter.cpp | 29 + src/wallet/WalletAsyncContextCounter.h | 30 + src/wallet/WalletErrors.cpp | 13 + src/wallet/WalletErrors.h | 73 ++ src/wallet/WalletEvent.h | 113 +++ src/wallet/WalletRequest.h | 95 +++ src/wallet/WalletSendTransactionContext.h | 40 + src/wallet/WalletSerialization.h | 80 ++ src/wallet/WalletSynchronizationContext.h | 42 + src/wallet/WalletSynchronizer.cpp | 475 +++++++++++ src/wallet/WalletSynchronizer.h | 83 ++ src/wallet/WalletTransactionSender.cpp | 299 +++++++ src/wallet/WalletTransactionSender.h | 55 ++ src/wallet/WalletTransferDetails.cpp | 169 ++++ src/wallet/WalletTransferDetails.h | 82 ++ src/wallet/WalletTxSendingState.cpp | 29 + src/wallet/WalletTxSendingState.h | 33 + src/wallet/WalletUnconfirmedTransactions.cpp | 45 ++ src/wallet/WalletUnconfirmedTransactions.h | 59 ++ src/wallet/WalletUserTransactionsCache.cpp | 166 ++++ src/wallet/WalletUserTransactionsCache.h | 77 ++ src/wallet/WalletUtils.h | 19 + src/wallet/wallet2.cpp | 23 +- src/wallet/wallet2.h | 1 + src/wallet/wallet_rpc_server.cpp | 45 +- src/wallet/wallet_rpc_server_commans_defs.h | 6 +- tests/CMakeLists.txt | 18 +- tests/core_proxy/core_proxy.cpp | 14 +- tests/core_proxy/core_proxy.h | 4 +- tests/core_tests/chain_switch_1.cpp | 6 +- tests/core_tests/chaingen.cpp | 6 +- tests/core_tests/chaingen.h | 4 +- tests/core_tests/double_spend.h | 2 +- .../transactions_flow_test.cpp | 6 +- tests/hash/main.cpp | 16 +- tests/io.h | 2 +- .../node_rpc_proxy_test.cpp | 132 ++++ tests/performance_tests/cn_slow_hash.h | 3 +- tests/performance_tests/main.cpp | 1 + tests/unit_tests/INodeStubs.cpp | 128 +++ tests/unit_tests/INodeStubs.h | 59 ++ tests/unit_tests/TestBlockchainGenerator.cpp | 113 +++ tests/unit_tests/TestBlockchainGenerator.h | 32 + tests/unit_tests/test_format_utils.cpp | 4 +- tests/unit_tests/test_wallet.cpp | 744 ++++++++++++++++++ 82 files changed, 5425 insertions(+), 726 deletions(-) create mode 100644 src/common/ObserverManager.h create mode 100644 src/crypto/slow-hash.cpp create mode 100644 src/crypto/slow-hash.inl create mode 100644 src/node_rpc_proxy/InitState.h create mode 100644 src/node_rpc_proxy/NodeErrors.cpp create mode 100644 src/node_rpc_proxy/NodeErrors.h create mode 100644 src/node_rpc_proxy/NodeRpcProxy.cpp create mode 100644 src/node_rpc_proxy/NodeRpcProxy.h create mode 100644 src/wallet/Wallet.cpp create mode 100644 src/wallet/Wallet.h create mode 100644 src/wallet/WalletAsyncContextCounter.cpp create mode 100644 src/wallet/WalletAsyncContextCounter.h create mode 100644 src/wallet/WalletErrors.cpp create mode 100644 src/wallet/WalletErrors.h create mode 100644 src/wallet/WalletEvent.h create mode 100644 src/wallet/WalletRequest.h create mode 100644 src/wallet/WalletSendTransactionContext.h create mode 100644 src/wallet/WalletSerialization.h create mode 100644 src/wallet/WalletSynchronizationContext.h create mode 100644 src/wallet/WalletSynchronizer.cpp create mode 100644 src/wallet/WalletSynchronizer.h create mode 100644 src/wallet/WalletTransactionSender.cpp create mode 100644 src/wallet/WalletTransactionSender.h create mode 100644 src/wallet/WalletTransferDetails.cpp create mode 100644 src/wallet/WalletTransferDetails.h create mode 100644 src/wallet/WalletTxSendingState.cpp create mode 100644 src/wallet/WalletTxSendingState.h create mode 100644 src/wallet/WalletUnconfirmedTransactions.cpp create mode 100644 src/wallet/WalletUnconfirmedTransactions.h create mode 100644 src/wallet/WalletUserTransactionsCache.cpp create mode 100644 src/wallet/WalletUserTransactionsCache.h create mode 100644 src/wallet/WalletUtils.h create mode 100644 tests/node_rpc_proxy_test/node_rpc_proxy_test.cpp create mode 100644 tests/unit_tests/INodeStubs.cpp create mode 100644 tests/unit_tests/INodeStubs.h create mode 100644 tests/unit_tests/TestBlockchainGenerator.cpp create mode 100644 tests/unit_tests/TestBlockchainGenerator.h create mode 100644 tests/unit_tests/test_wallet.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 26c78e84..6dd3638a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) set(CMAKE_CONFIGURATION_TYPES "Debug;Release") enable_testing() -include_directories(src contrib/epee/include external "${CMAKE_BINARY_DIR}/version") +include_directories(include src contrib/epee/include external "${CMAKE_BINARY_DIR}/version") if(APPLE) include_directories(SYSTEM /usr/include/malloc) endif() @@ -15,7 +15,7 @@ endif() set(STATIC ${MSVC} CACHE BOOL "Link libraries statically") if(MSVC) - add_definitions("/bigobj /MP /W3 /GS- /D_CRT_SECURE_NO_WARNINGS /wd4996 /wd4345 /D_WIN32_WINNT=0x0600 /DWIN32_LEAN_AND_MEAN /DGTEST_HAS_TR1_TUPLE=0 /FIinline_c.h /D__SSE4_1__") + add_definitions("/bigobj /MP /W3 /GS- /D_CRT_SECURE_NO_WARNINGS /wd4996 /wd4345 /D_WIN32_WINNT=0x0600 /DWIN32_LEAN_AND_MEAN /DGTEST_HAS_TR1_TUPLE=0 /D_VARIADIC_MAX=8 /FIinline_c.h /D__SSE4_1__") # set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Dinline=__inline") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /STACK:10485760") if(STATIC) diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt index 90c74c3b..69836bef 100644 --- a/ReleaseNotes.txt +++ b/ReleaseNotes.txt @@ -1,3 +1,11 @@ +Release notes 0.8.11 + +- Increased minimum transaction fee +- Transaction pool optimizations +- High level API implementation +- CryptoNight hash function optimization +- Improvements for wallet JSON RPC API + Release notes 0.8.10 - Optimized blockchain storage memory usage diff --git a/include/INode.h b/include/INode.h index 624d4ac4..4ee6ce3b 100644 --- a/include/INode.h +++ b/include/INode.h @@ -5,33 +5,54 @@ #pragma once #include +#include #include +#include + +#include "crypto/crypto.h" +#include "cryptonote_core/cryptonote_basic.h" +#include "cryptonote_protocol/cryptonote_protocol_defs.h" +#include "rpc/core_rpc_server_commands_defs.h" namespace CryptoNote { class INodeObserver { public: - virtual void initCompleted(std::error_code result) {} - + virtual ~INodeObserver() {} virtual void peerCountUpdated(size_t count) {} - virtual void lastLocalBlockHeightUpdated(uint64_t height) {} + virtual void localBlockchainUpdated(uint64_t height) {} virtual void lastKnownBlockHeightUpdated(uint64_t height) {} +}; - virtual void blockchainReorganized(uint64_t height) {} +struct OutEntry { + uint64_t outGlobalIndex; + crypto::public_key outKey; +}; + +struct OutsForAmount { + uint64_t amount; + std::vector outs; }; class INode { public: - virtual ~INode() = 0; - virtual void addObserver(INodeObserver* observer) = 0; - virtual void removeObserver(INodeObserver* observer) = 0; + typedef std::function Callback; - virtual void init() = 0; - virtual void shutdown() = 0; + virtual ~INode() {} + virtual bool addObserver(INodeObserver* observer) = 0; + virtual bool removeObserver(INodeObserver* observer) = 0; - virtual size_t getPeerCount() = 0; - virtual uint64_t getLastLocalBlockHeight() = 0; - virtual uint64_t getLastKnownBlockHeight() = 0; + virtual void init(const Callback& callback) = 0; + virtual bool shutdown() = 0; + + virtual size_t getPeerCount() const = 0; + 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 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 32d5cad1..a89538d8 100644 --- a/include/IWallet.h +++ b/include/IWallet.h @@ -17,7 +17,7 @@ namespace CryptoNote { typedef size_t TransactionId; typedef size_t TransferId; -typedef std::array TransacitonHash; +typedef std::array TransactionHash; struct Transfer { std::string address; @@ -33,7 +33,7 @@ struct Transaction { size_t transferCount; int64_t totalAmount; uint64_t fee; - TransacitonHash hash; + TransactionHash hash; bool isCoinbase; uint64_t blockHeight; uint64_t timestamp; @@ -44,7 +44,7 @@ class IWalletObserver { public: virtual void initCompleted(std::error_code result) {} virtual void saveCompleted(std::error_code result) {} - virtual void synchronizationProgressUpdated(uint64_t current, uint64_t total) {} + virtual void synchronizationProgressUpdated(uint64_t current, uint64_t total, std::error_code result) {} virtual void actualBalanceUpdated(uint64_t actualBalance) {} virtual void pendingBalanceUpdated(uint64_t pendingBalance) {} virtual void externalTransactionCreated(TransactionId transactionId) {} @@ -54,7 +54,7 @@ public: class IWallet { public: - virtual ~IWallet() = 0; + virtual ~IWallet() {} ; virtual void addObserver(IWalletObserver* observer) = 0; virtual void removeObserver(IWalletObserver* observer) = 0; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f890fcda..0a446320 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,6 +11,7 @@ file(GLOB_RECURSE SIMPLEWALLET simplewallet/*) file(GLOB_RECURSE CONN_TOOL connectivity_tool/*) file(GLOB_RECURSE WALLET wallet/*) file(GLOB_RECURSE MINER miner/*) +file(GLOB_RECURSE NODE_RPC_PROXY node_rpc_proxy/*) source_group(common FILES ${COMMON}) source_group(crypto FILES ${CRYPTO}) @@ -23,6 +24,7 @@ source_group(simplewallet FILES ${SIMPLEWALLET}) source_group(connectivity-tool FILES ${CONN_TOOL}) source_group(wallet FILES ${WALLET}) source_group(simpleminer FILES ${MINER}) +source_group(node_rpc_proxy FILES ${NODE_RPC_PROXY}) add_library(common ${COMMON}) add_library(crypto ${CRYPTO}) @@ -37,10 +39,12 @@ 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}) +add_library(node_rpc_proxy ${NODE_RPC_PROXY}) + add_dependencies(daemon version) add_dependencies(rpc version) add_dependencies(simplewallet version) -set_property(TARGET common crypto cryptonote_core rpc wallet PROPERTY FOLDER "libs") +set_property(TARGET common crypto cryptonote_core rpc wallet node_rpc_proxy PROPERTY FOLDER "libs") set_property(TARGET daemon simplewallet connectivity_tool simpleminer PROPERTY FOLDER "prog") set_property(TARGET daemon PROPERTY OUTPUT_NAME "bytecoind") diff --git a/src/common/ObserverManager.h b/src/common/ObserverManager.h new file mode 100644 index 00000000..02b59d9f --- /dev/null +++ b/src/common/ObserverManager.h @@ -0,0 +1,116 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include +#include + +namespace tools { + +template +class ObserverManager { +public: + bool add(T* observer) { + std::unique_lock lock(m_observersMutex); + auto it = std::find(m_observers.begin(), m_observers.end(), observer); + if (m_observers.end() == it) { + m_observers.push_back(observer); + return true; + } else { + return false; + } + } + + bool remove(T* observer) { + std::unique_lock lock(m_observersMutex); + auto it = std::find(m_observers.begin(), m_observers.end(), observer); + if (m_observers.end() == it) { + return false; + } else { + m_observers.erase(it); + return true; + } + } + + void clear() { + std::unique_lock lock(m_observersMutex); + m_observers.clear(); + } + +#if defined(_MSC_VER) + template + void notify(F notification) { + std::vector observersCopy; + { + std::unique_lock lock(m_observersMutex); + observersCopy = m_observers; + } + + for (T* observer : observersCopy) { + (observer->*notification)(); + } + } + + template + void notify(F notification, const Arg0& arg0) { + std::vector observersCopy; + { + std::unique_lock lock(m_observersMutex); + observersCopy = m_observers; + } + + for (T* observer : observersCopy) { + (observer->*notification)(arg0); + } + } + + template + void notify(F notification, const Arg0& arg0, const Arg1& arg1) { + std::vector observersCopy; + { + std::unique_lock lock(m_observersMutex); + observersCopy = m_observers; + } + + for (T* observer : observersCopy) { + (observer->*notification)(arg0, arg1); + } + } + + template + void notify(F notification, const Arg0& arg0, const Arg1& arg1, const Arg2& arg2) { + std::vector observersCopy; + { + std::unique_lock lock(m_observersMutex); + observersCopy = m_observers; + } + + for (T* observer : observersCopy) { + (observer->*notification)(arg0, arg1, arg2); + } + } + +#else + + template + void notify(F notification, Args... args) { + std::vector observersCopy; + { + std::unique_lock lock(m_observersMutex); + observersCopy = m_observers; + } + + for (T* observer : observersCopy) { + (observer->*notification)(args...); + } + } +#endif + +private: + std::vector m_observers; + std::mutex m_observersMutex; +}; + +} diff --git a/src/common/base58.cpp b/src/common/base58.cpp index 454c0db6..575279aa 100644 --- a/src/common/base58.cpp +++ b/src/common/base58.cpp @@ -110,7 +110,7 @@ namespace tools void encode_block(const char* block, size_t size, char* res) { - assert(1 <= size && size <= sizeof(full_block_size)); + assert(1 <= size && size <= full_block_size); uint64_t num = uint_8be_to_64(reinterpret_cast(block), size); int i = static_cast(encoded_block_sizes[size]) - 1; diff --git a/src/crypto/chacha8.h b/src/crypto/chacha8.h index e4fe4679..72f84e62 100644 --- a/src/crypto/chacha8.h +++ b/src/crypto/chacha8.h @@ -44,13 +44,13 @@ namespace crypto { chacha8(data, length, reinterpret_cast(&key), reinterpret_cast(&iv), cipher); } - inline void generate_chacha8_key(std::string password, chacha8_key& key) { + inline void generate_chacha8_key(crypto::cn_context &context, std::string password, chacha8_key& key) { static_assert(sizeof(chacha8_key) <= sizeof(hash), "Size of hash must be at least that of chacha8_key"); - char pwd_hash[HASH_SIZE]; - crypto::cn_slow_hash(password.data(), password.size(), pwd_hash); - memcpy(&key, pwd_hash, sizeof(key)); - memset(pwd_hash, 0, sizeof(pwd_hash)); + crypto::hash pwd_hash; + crypto::cn_slow_hash(context, password.data(), password.size(), pwd_hash); + memcpy(&key, &pwd_hash, sizeof(key)); + memset(&pwd_hash, 0, sizeof(pwd_hash)); } } -#endif +#endif \ No newline at end of file diff --git a/src/crypto/hash-ops.h b/src/crypto/hash-ops.h index 9e6c821e..c3aacb60 100644 --- a/src/crypto/hash-ops.h +++ b/src/crypto/hash-ops.h @@ -49,11 +49,13 @@ void hash_process(union hash_state *state, const uint8_t *buf, size_t count); enum { HASH_SIZE = 32, - HASH_DATA_AREA = 136 + HASH_DATA_AREA = 136, + SLOW_HASH_CONTEXT_SIZE = 2097552 }; void cn_fast_hash(const void *data, size_t length, char *hash); -void cn_slow_hash(const void *data, size_t length, char *hash); + +void cn_slow_hash_f(void *, const void *, size_t, void *); void hash_extra_blake(const void *data, size_t length, char *hash); void hash_extra_groestl(const void *data, size_t length, char *hash); diff --git a/src/crypto/hash.h b/src/crypto/hash.h index fb65494b..bac2d266 100644 --- a/src/crypto/hash.h +++ b/src/crypto/hash.h @@ -37,8 +37,24 @@ namespace crypto { return h; } - inline void cn_slow_hash(const void *data, std::size_t length, hash &hash) { - cn_slow_hash(data, length, reinterpret_cast(&hash)); + class cn_context { + public: + + cn_context(); + ~cn_context(); +#if !defined(_MSC_VER) || _MSC_VER >= 1800 + cn_context(const cn_context &) = delete; + void operator=(const cn_context &) = delete; +#endif + + private: + + void *data; + friend inline void cn_slow_hash(cn_context &, const void *, std::size_t, hash &); + }; + + inline void cn_slow_hash(cn_context &context, const void *data, std::size_t length, hash &hash) { + (*cn_slow_hash_f)(context.data, data, length, reinterpret_cast(&hash)); } inline void tree_hash(const hash *hashes, std::size_t count, hash &root_hash) { @@ -47,4 +63,4 @@ namespace crypto { } -CRYPTO_MAKE_HASHABLE(hash) +CRYPTO_MAKE_HASHABLE(hash) \ No newline at end of file diff --git a/src/crypto/slow-hash.c b/src/crypto/slow-hash.c index 468ba644..44c6a1f0 100644 --- a/src/crypto/slow-hash.c +++ b/src/crypto/slow-hash.c @@ -2,257 +2,179 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include #include #include #include +#include +#include + +#if defined(_MSC_VER) +#include +#else +#include +#endif + #include "aesb.h" +#include "initializer.h" #include "common/int-util.h" #include "hash-ops.h" #include "oaes_lib.h" -#include +void (*cn_slow_hash_fp)(void *, const void *, size_t, void *); -#if defined(_MSC_VER) || defined(__INTEL_COMPILER) -#include -#define STATIC -#define INLINE __inline -#if !defined(RDATA_ALIGN16) -#define RDATA_ALIGN16 __declspec(align(16)) -#endif +void cn_slow_hash_f(void * a, const void * b, size_t c, void * d){ +(*cn_slow_hash_fp)(a, b, c, d); +} + +#if defined(__GNUC__) +#define likely(x) (__builtin_expect(!!(x), 1)) +#define unlikely(x) (__builtin_expect(!!(x), 0)) #else -#include -#define STATIC static -#define INLINE inline -#if !defined(RDATA_ALIGN16) -#define RDATA_ALIGN16 __attribute__ ((aligned(16))) -#endif +#define likely(x) (x) +#define unlikely(x) (x) +#define __attribute__(x) #endif -#define MEMORY (1 << 21) // 2MB scratchpad +#if defined(_MSC_VER) +#define restrict +#endif + +#define MEMORY (1 << 21) /* 2 MiB */ #define ITER (1 << 20) #define AES_BLOCK_SIZE 16 -#define AES_KEY_SIZE 32 +#define AES_KEY_SIZE 32 /*16*/ #define INIT_SIZE_BLK 8 -#define INIT_SIZE_BYTE (INIT_SIZE_BLK * AES_BLOCK_SIZE) - -#define U64(x) ((uint64_t *) (x)) -#define R128(x) ((__m128i *) (x)) +#define INIT_SIZE_BYTE (INIT_SIZE_BLK * AES_BLOCK_SIZE) // 128 #pragma pack(push, 1) -union cn_slow_hash_state -{ - union hash_state hs; - struct - { - uint8_t k[64]; - uint8_t init[INIT_SIZE_BYTE]; - }; +union cn_slow_hash_state { + union hash_state hs; + struct { + uint8_t k[64]; + uint8_t init[INIT_SIZE_BYTE]; + }; }; #pragma pack(pop) -#if defined(_MSC_VER) || defined(__INTEL_COMPILER) -#define cpuid(info,x) __cpuidex(info,x,0) -#else -void cpuid(int CPUInfo[4], int InfoType) -{ - __asm__ __volatile__ - ( - "cpuid": - "=a" (CPUInfo[0]), - "=b" (CPUInfo[1]), - "=c" (CPUInfo[2]), - "=d" (CPUInfo[3]) : - "a" (InfoType), "c" (0) - ); -} +#if defined(_MSC_VER) +#define ALIGNED_DATA(x) __declspec(align(x)) +#define ALIGNED_DECL(t, x) ALIGNED_DATA(x) t +#elif defined(__GNUC__) +#define ALIGNED_DATA(x) __attribute__((aligned(x))) +#define ALIGNED_DECL(t, x) t ALIGNED_DATA(x) #endif -STATIC INLINE void mul(const uint8_t *a, const uint8_t *b, uint8_t *res) -{ - uint64_t a0, b0; - uint64_t hi, lo; +struct cn_ctx { + ALIGNED_DECL(uint8_t long_state[MEMORY], 16); + ALIGNED_DECL(union cn_slow_hash_state state, 16); + ALIGNED_DECL(uint8_t text[INIT_SIZE_BYTE], 16); + ALIGNED_DECL(uint64_t a[AES_BLOCK_SIZE >> 3], 16); + ALIGNED_DECL(uint64_t b[AES_BLOCK_SIZE >> 3], 16); + ALIGNED_DECL(uint8_t c[AES_BLOCK_SIZE], 16); + oaes_ctx* aes_ctx; +}; - a0 = U64(a)[0]; - b0 = U64(b)[0]; - lo = mul128(a0, b0, &hi); - U64(res)[0] = hi; - U64(res)[1] = lo; +static_assert(sizeof(struct cn_ctx) == SLOW_HASH_CONTEXT_SIZE, "Invalid structure size"); + +static inline void ExpandAESKey256_sub1(__m128i *tmp1, __m128i *tmp2) +{ + __m128i tmp4; + *tmp2 = _mm_shuffle_epi32(*tmp2, 0xFF); + tmp4 = _mm_slli_si128(*tmp1, 0x04); + *tmp1 = _mm_xor_si128(*tmp1, tmp4); + tmp4 = _mm_slli_si128(tmp4, 0x04); + *tmp1 = _mm_xor_si128(*tmp1, tmp4); + tmp4 = _mm_slli_si128(tmp4, 0x04); + *tmp1 = _mm_xor_si128(*tmp1, tmp4); + *tmp1 = _mm_xor_si128(*tmp1, *tmp2); } -STATIC INLINE void sum_half_blocks(uint8_t *a, const uint8_t *b) +static inline void ExpandAESKey256_sub2(__m128i *tmp1, __m128i *tmp3) { - uint64_t a0, a1, b0, b1; - a0 = U64(a)[0]; - a1 = U64(a)[1]; - b0 = U64(b)[0]; - b1 = U64(b)[1]; - a0 += b0; - a1 += b1; - U64(a)[0] = a0; - U64(a)[1] = a1; + __m128i tmp2, tmp4; + + tmp4 = _mm_aeskeygenassist_si128(*tmp1, 0x00); + tmp2 = _mm_shuffle_epi32(tmp4, 0xAA); + tmp4 = _mm_slli_si128(*tmp3, 0x04); + *tmp3 = _mm_xor_si128(*tmp3, tmp4); + tmp4 = _mm_slli_si128(tmp4, 0x04); + *tmp3 = _mm_xor_si128(*tmp3, tmp4); + tmp4 = _mm_slli_si128(tmp4, 0x04); + *tmp3 = _mm_xor_si128(*tmp3, tmp4); + *tmp3 = _mm_xor_si128(*tmp3, tmp2); } -STATIC INLINE void swap_blocks(uint8_t *a, uint8_t *b) +// Special thanks to Intel for helping me +// with ExpandAESKey256() and its subroutines +static inline void ExpandAESKey256(uint8_t *keybuf) { - uint64_t t[2]; - U64(t)[0] = U64(a)[0]; - U64(t)[1] = U64(a)[1]; - U64(a)[0] = U64(b)[0]; - U64(a)[1] = U64(b)[1]; - U64(b)[0] = U64(t)[0]; - U64(b)[1] = U64(t)[1]; + __m128i tmp1, tmp2, tmp3, *keys; + + keys = (__m128i *)keybuf; + + tmp1 = _mm_load_si128((__m128i *)keybuf); + tmp3 = _mm_load_si128((__m128i *)(keybuf+0x10)); + + tmp2 = _mm_aeskeygenassist_si128(tmp3, 0x01); + ExpandAESKey256_sub1(&tmp1, &tmp2); + keys[2] = tmp1; + ExpandAESKey256_sub2(&tmp1, &tmp3); + keys[3] = tmp3; + + tmp2 = _mm_aeskeygenassist_si128(tmp3, 0x02); + ExpandAESKey256_sub1(&tmp1, &tmp2); + keys[4] = tmp1; + ExpandAESKey256_sub2(&tmp1, &tmp3); + keys[5] = tmp3; + + tmp2 = _mm_aeskeygenassist_si128(tmp3, 0x04); + ExpandAESKey256_sub1(&tmp1, &tmp2); + keys[6] = tmp1; + ExpandAESKey256_sub2(&tmp1, &tmp3); + keys[7] = tmp3; + + tmp2 = _mm_aeskeygenassist_si128(tmp3, 0x08); + ExpandAESKey256_sub1(&tmp1, &tmp2); + keys[8] = tmp1; + ExpandAESKey256_sub2(&tmp1, &tmp3); + keys[9] = tmp3; + + tmp2 = _mm_aeskeygenassist_si128(tmp3, 0x10); + ExpandAESKey256_sub1(&tmp1, &tmp2); + keys[10] = tmp1; + ExpandAESKey256_sub2(&tmp1, &tmp3); + keys[11] = tmp3; + + tmp2 = _mm_aeskeygenassist_si128(tmp3, 0x20); + ExpandAESKey256_sub1(&tmp1, &tmp2); + keys[12] = tmp1; + ExpandAESKey256_sub2(&tmp1, &tmp3); + keys[13] = tmp3; + + tmp2 = _mm_aeskeygenassist_si128(tmp3, 0x40); + ExpandAESKey256_sub1(&tmp1, &tmp2); + keys[14] = tmp1; } -STATIC INLINE void xor_blocks(uint8_t *a, const uint8_t *b) +static void (*const extra_hashes[4])(const void *, size_t, char *) = { - U64(a)[0] ^= U64(b)[0]; - U64(a)[1] ^= U64(b)[1]; -} - -STATIC INLINE int check_aes_hw(void) -{ - int cpuid_results[4]; - static int supported = -1; - - if(supported >= 0) - return supported; - - cpuid(cpuid_results,1); - return supported = cpuid_results[2] & (1 << 25); -} - -STATIC INLINE void aesni_pseudo_round(const uint8_t *in, uint8_t *out, - const uint8_t *expandedKey) -{ - __m128i *k = R128(expandedKey); - __m128i d; - - d = _mm_loadu_si128(R128(in)); - d = _mm_aesenc_si128(d, *R128(&k[0])); - d = _mm_aesenc_si128(d, *R128(&k[1])); - d = _mm_aesenc_si128(d, *R128(&k[2])); - d = _mm_aesenc_si128(d, *R128(&k[3])); - d = _mm_aesenc_si128(d, *R128(&k[4])); - d = _mm_aesenc_si128(d, *R128(&k[5])); - d = _mm_aesenc_si128(d, *R128(&k[6])); - d = _mm_aesenc_si128(d, *R128(&k[7])); - d = _mm_aesenc_si128(d, *R128(&k[8])); - d = _mm_aesenc_si128(d, *R128(&k[9])); - _mm_storeu_si128((R128(out)), d); -} - -void cn_slow_hash(const void *data, size_t length, char *hash) -{ - uint8_t long_state[MEMORY]; - uint8_t text[INIT_SIZE_BYTE]; - uint8_t a[AES_BLOCK_SIZE]; - uint8_t b[AES_BLOCK_SIZE]; - uint8_t d[AES_BLOCK_SIZE]; - RDATA_ALIGN16 uint8_t expandedKey[256]; - - union cn_slow_hash_state state; - - size_t i, j; - uint8_t *p = NULL; - oaes_ctx *aes_ctx; - - int useAes = check_aes_hw(); - static void (*const extra_hashes[4])(const void *, size_t, char *) = - { - hash_extra_blake, hash_extra_groestl, hash_extra_jh, hash_extra_skein - }; - - hash_process(&state.hs, data, length); - memcpy(text, state.init, INIT_SIZE_BYTE); - - aes_ctx = (oaes_ctx *) oaes_alloc(); - oaes_key_import_data(aes_ctx, state.hs.b, AES_KEY_SIZE); - - // use aligned data - memcpy(expandedKey, aes_ctx->key->exp_data, aes_ctx->key->exp_data_len); - - if(useAes) - { - for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) - { - for(j = 0; j < INIT_SIZE_BLK; j++) - aesni_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], expandedKey); - memcpy(&long_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE); - } - } - else - { - for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) - { - for(j = 0; j < INIT_SIZE_BLK; j++) - aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], expandedKey); - - memcpy(&long_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE); - } - } - - U64(a)[0] = U64(&state.k[0])[0] ^ U64(&state.k[32])[0]; - U64(a)[1] = U64(&state.k[0])[1] ^ U64(&state.k[32])[1]; - U64(b)[0] = U64(&state.k[16])[0] ^ U64(&state.k[48])[0]; - U64(b)[1] = U64(&state.k[16])[1] ^ U64(&state.k[48])[1]; - - for(i = 0; i < ITER / 2; i++) - { - #define TOTALBLOCKS (MEMORY / AES_BLOCK_SIZE) - #define state_index(x) (((*((uint64_t *)x) >> 4) & (TOTALBLOCKS - 1)) << 4) - - // Iteration 1 - p = &long_state[state_index(a)]; - - if(useAes) - _mm_storeu_si128(R128(p), _mm_aesenc_si128(_mm_loadu_si128(R128(p)), _mm_loadu_si128(R128(a)))); - else - aesb_single_round(p, p, a); - - xor_blocks(b, p); - swap_blocks(b, p); - swap_blocks(a, b); - - // Iteration 2 - p = &long_state[state_index(a)]; - - mul(a, p, d); - sum_half_blocks(b, d); - swap_blocks(b, p); - xor_blocks(b, p); - swap_blocks(a, b); - } - - memcpy(text, state.init, INIT_SIZE_BYTE); - oaes_key_import_data(aes_ctx, &state.hs.b[32], AES_KEY_SIZE); - memcpy(expandedKey, aes_ctx->key->exp_data, aes_ctx->key->exp_data_len); - if(useAes) - { - for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) - { - for(j = 0; j < INIT_SIZE_BLK; j++) - { - xor_blocks(&text[j * AES_BLOCK_SIZE], &long_state[i * INIT_SIZE_BYTE + j * AES_BLOCK_SIZE]); - aesni_pseudo_round(&text[j * AES_BLOCK_SIZE], &text[j * AES_BLOCK_SIZE], expandedKey); - } - } - } - else - { - for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) - { - for(j = 0; j < INIT_SIZE_BLK; j++) - { - xor_blocks(&text[j * AES_BLOCK_SIZE], &long_state[i * INIT_SIZE_BYTE + j * AES_BLOCK_SIZE]); - aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], expandedKey); - } - } - } - - oaes_free((OAES_CTX **) &aes_ctx); - memcpy(state.init, text, INIT_SIZE_BYTE); - hash_permutation(&state.hs); - extra_hashes[state.hs.b[0] & 3](&state, 200, hash); + hash_extra_blake, hash_extra_groestl, hash_extra_jh, hash_extra_skein +}; + +#include "slow-hash.inl" +#define AESNI +#include "slow-hash.inl" + +INITIALIZER(detect_aes) { + int ecx; +#if defined(_MSC_VER) + int cpuinfo[4]; + __cpuid(cpuinfo, 1); + ecx = cpuinfo[2]; +#else + int a, b, d; + __cpuid(1, a, b, ecx, d); +#endif + cn_slow_hash_fp = (ecx & (1 << 25)) ? &cn_slow_hash_aesni : &cn_slow_hash_noaesni; } diff --git a/src/crypto/slow-hash.cpp b/src/crypto/slow-hash.cpp new file mode 100644 index 00000000..7141359a --- /dev/null +++ b/src/crypto/slow-hash.cpp @@ -0,0 +1,60 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include "hash.h" + +#if defined(WIN32) +#include +#else +#include +#endif + +using std::bad_alloc; + +namespace crypto { + + enum { + MAP_SIZE = SLOW_HASH_CONTEXT_SIZE + ((-SLOW_HASH_CONTEXT_SIZE) & 0xfff) + }; + +#if defined(WIN32) + + cn_context::cn_context() { + data = VirtualAlloc(nullptr, MAP_SIZE, MEM_COMMIT, PAGE_READWRITE); + if (data == nullptr) { + throw bad_alloc(); + } + } + + cn_context::~cn_context() { + if (!VirtualFree(data, 0, MEM_RELEASE)) { + throw bad_alloc(); + } + } + +#else + + cn_context::cn_context() { +#if !defined(__APPLE__) + data = mmap(nullptr, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0); +#else + data = mmap(nullptr, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); +#endif + if (data == MAP_FAILED) { + throw bad_alloc(); + } + mlock(data, MAP_SIZE); + } + + cn_context::~cn_context() { + if (munmap(data, MAP_SIZE) != 0) { + throw bad_alloc(); + } + } + +#endif + +} diff --git a/src/crypto/slow-hash.inl b/src/crypto/slow-hash.inl new file mode 100644 index 00000000..0d9c3573 --- /dev/null +++ b/src/crypto/slow-hash.inl @@ -0,0 +1,184 @@ +static void +#if defined(AESNI) +cn_slow_hash_aesni +#else +cn_slow_hash_noaesni +#endif +(void *restrict context, const void *restrict data, size_t length, void *restrict hash) +{ +#define ctx ((struct cn_ctx *) context) + uint8_t ExpandedKey[256]; + size_t i, j; + __m128i *longoutput, *expkey, *xmminput, b_x; + ALIGNED_DECL(uint64_t a[2], 16); + hash_process(&ctx->state.hs, (const uint8_t*) data, length); + + memcpy(ctx->text, ctx->state.init, INIT_SIZE_BYTE); +#if defined(AESNI) + memcpy(ExpandedKey, ctx->state.hs.b, AES_KEY_SIZE); + ExpandAESKey256(ExpandedKey); +#else + ctx->aes_ctx = oaes_alloc(); + oaes_key_import_data(ctx->aes_ctx, ctx->state.hs.b, AES_KEY_SIZE); + memcpy(ExpandedKey, ctx->aes_ctx->key->exp_data, ctx->aes_ctx->key->exp_data_len); +#endif + + longoutput = (__m128i *) ctx->long_state; + expkey = (__m128i *) ExpandedKey; + xmminput = (__m128i *) ctx->text; + + //for (i = 0; likely(i < MEMORY); i += INIT_SIZE_BYTE) + // aesni_parallel_noxor(&ctx->long_state[i], ctx->text, ExpandedKey); + + for (i = 0; likely(i < MEMORY); i += INIT_SIZE_BYTE) + { +#if defined(AESNI) + for(j = 0; j < 10; j++) + { + xmminput[0] = _mm_aesenc_si128(xmminput[0], expkey[j]); + xmminput[1] = _mm_aesenc_si128(xmminput[1], expkey[j]); + xmminput[2] = _mm_aesenc_si128(xmminput[2], expkey[j]); + xmminput[3] = _mm_aesenc_si128(xmminput[3], expkey[j]); + xmminput[4] = _mm_aesenc_si128(xmminput[4], expkey[j]); + xmminput[5] = _mm_aesenc_si128(xmminput[5], expkey[j]); + xmminput[6] = _mm_aesenc_si128(xmminput[6], expkey[j]); + xmminput[7] = _mm_aesenc_si128(xmminput[7], expkey[j]); + } +#else + aesb_pseudo_round((uint8_t *) &xmminput[0], (uint8_t *) &xmminput[0], (uint8_t *) expkey); + aesb_pseudo_round((uint8_t *) &xmminput[1], (uint8_t *) &xmminput[1], (uint8_t *) expkey); + aesb_pseudo_round((uint8_t *) &xmminput[2], (uint8_t *) &xmminput[2], (uint8_t *) expkey); + aesb_pseudo_round((uint8_t *) &xmminput[3], (uint8_t *) &xmminput[3], (uint8_t *) expkey); + aesb_pseudo_round((uint8_t *) &xmminput[4], (uint8_t *) &xmminput[4], (uint8_t *) expkey); + aesb_pseudo_round((uint8_t *) &xmminput[5], (uint8_t *) &xmminput[5], (uint8_t *) expkey); + aesb_pseudo_round((uint8_t *) &xmminput[6], (uint8_t *) &xmminput[6], (uint8_t *) expkey); + aesb_pseudo_round((uint8_t *) &xmminput[7], (uint8_t *) &xmminput[7], (uint8_t *) expkey); +#endif + _mm_store_si128(&(longoutput[(i >> 4)]), xmminput[0]); + _mm_store_si128(&(longoutput[(i >> 4) + 1]), xmminput[1]); + _mm_store_si128(&(longoutput[(i >> 4) + 2]), xmminput[2]); + _mm_store_si128(&(longoutput[(i >> 4) + 3]), xmminput[3]); + _mm_store_si128(&(longoutput[(i >> 4) + 4]), xmminput[4]); + _mm_store_si128(&(longoutput[(i >> 4) + 5]), xmminput[5]); + _mm_store_si128(&(longoutput[(i >> 4) + 6]), xmminput[6]); + _mm_store_si128(&(longoutput[(i >> 4) + 7]), xmminput[7]); + } + + for (i = 0; i < 2; i++) + { + ctx->a[i] = ((uint64_t *)ctx->state.k)[i] ^ ((uint64_t *)ctx->state.k)[i+4]; + ctx->b[i] = ((uint64_t *)ctx->state.k)[i+2] ^ ((uint64_t *)ctx->state.k)[i+6]; + } + + b_x = _mm_load_si128((__m128i *)ctx->b); + a[0] = ctx->a[0]; + a[1] = ctx->a[1]; + + for(i = 0; likely(i < 0x80000); i++) + { + __m128i c_x = _mm_load_si128((__m128i *)&ctx->long_state[a[0] & 0x1FFFF0]); + __m128i a_x = _mm_load_si128((__m128i *)a); + ALIGNED_DECL(uint64_t c[2], 16); + ALIGNED_DECL(uint64_t b[2], 16); + uint64_t *nextblock, *dst; + +#if defined(AESNI) + c_x = _mm_aesenc_si128(c_x, a_x); +#else + aesb_single_round((uint8_t *) &c_x, (uint8_t *) &c_x, (uint8_t *) &a_x); +#endif + + _mm_store_si128((__m128i *)c, c_x); + //__builtin_prefetch(&ctx->long_state[c[0] & 0x1FFFF0], 0, 1); + + b_x = _mm_xor_si128(b_x, c_x); + _mm_store_si128((__m128i *)&ctx->long_state[a[0] & 0x1FFFF0], b_x); + + nextblock = (uint64_t *)&ctx->long_state[c[0] & 0x1FFFF0]; + b[0] = nextblock[0]; + b[1] = nextblock[1]; + + { + uint64_t hi, lo; + // hi,lo = 64bit x 64bit multiply of c[0] and b[0] + +#if defined(__GNUC__) && defined(__x86_64__) + __asm__("mulq %3\n\t" + : "=d" (hi), + "=a" (lo) + : "%a" (c[0]), + "rm" (b[0]) + : "cc" ); +#else + lo = mul128(c[0], b[0], &hi); +#endif + + a[0] += hi; + a[1] += lo; + } + dst = (uint64_t *) &ctx->long_state[c[0] & 0x1FFFF0]; + dst[0] = a[0]; + dst[1] = a[1]; + + a[0] ^= b[0]; + a[1] ^= b[1]; + b_x = c_x; + //__builtin_prefetch(&ctx->long_state[a[0] & 0x1FFFF0], 0, 3); + } + + memcpy(ctx->text, ctx->state.init, INIT_SIZE_BYTE); +#if defined(AESNI) + memcpy(ExpandedKey, &ctx->state.hs.b[32], AES_KEY_SIZE); + ExpandAESKey256(ExpandedKey); +#else + oaes_key_import_data(ctx->aes_ctx, &ctx->state.hs.b[32], AES_KEY_SIZE); + memcpy(ExpandedKey, ctx->aes_ctx->key->exp_data, ctx->aes_ctx->key->exp_data_len); +#endif + + //for (i = 0; likely(i < MEMORY); i += INIT_SIZE_BYTE) + // aesni_parallel_xor(&ctx->text, ExpandedKey, &ctx->long_state[i]); + + for (i = 0; likely(i < MEMORY); i += INIT_SIZE_BYTE) + { + xmminput[0] = _mm_xor_si128(longoutput[(i >> 4)], xmminput[0]); + xmminput[1] = _mm_xor_si128(longoutput[(i >> 4) + 1], xmminput[1]); + xmminput[2] = _mm_xor_si128(longoutput[(i >> 4) + 2], xmminput[2]); + xmminput[3] = _mm_xor_si128(longoutput[(i >> 4) + 3], xmminput[3]); + xmminput[4] = _mm_xor_si128(longoutput[(i >> 4) + 4], xmminput[4]); + xmminput[5] = _mm_xor_si128(longoutput[(i >> 4) + 5], xmminput[5]); + xmminput[6] = _mm_xor_si128(longoutput[(i >> 4) + 6], xmminput[6]); + xmminput[7] = _mm_xor_si128(longoutput[(i >> 4) + 7], xmminput[7]); + +#if defined(AESNI) + for(j = 0; j < 10; j++) + { + xmminput[0] = _mm_aesenc_si128(xmminput[0], expkey[j]); + xmminput[1] = _mm_aesenc_si128(xmminput[1], expkey[j]); + xmminput[2] = _mm_aesenc_si128(xmminput[2], expkey[j]); + xmminput[3] = _mm_aesenc_si128(xmminput[3], expkey[j]); + xmminput[4] = _mm_aesenc_si128(xmminput[4], expkey[j]); + xmminput[5] = _mm_aesenc_si128(xmminput[5], expkey[j]); + xmminput[6] = _mm_aesenc_si128(xmminput[6], expkey[j]); + xmminput[7] = _mm_aesenc_si128(xmminput[7], expkey[j]); + } +#else + aesb_pseudo_round((uint8_t *) &xmminput[0], (uint8_t *) &xmminput[0], (uint8_t *) expkey); + aesb_pseudo_round((uint8_t *) &xmminput[1], (uint8_t *) &xmminput[1], (uint8_t *) expkey); + aesb_pseudo_round((uint8_t *) &xmminput[2], (uint8_t *) &xmminput[2], (uint8_t *) expkey); + aesb_pseudo_round((uint8_t *) &xmminput[3], (uint8_t *) &xmminput[3], (uint8_t *) expkey); + aesb_pseudo_round((uint8_t *) &xmminput[4], (uint8_t *) &xmminput[4], (uint8_t *) expkey); + aesb_pseudo_round((uint8_t *) &xmminput[5], (uint8_t *) &xmminput[5], (uint8_t *) expkey); + aesb_pseudo_round((uint8_t *) &xmminput[6], (uint8_t *) &xmminput[6], (uint8_t *) expkey); + aesb_pseudo_round((uint8_t *) &xmminput[7], (uint8_t *) &xmminput[7], (uint8_t *) expkey); +#endif + + } + +#if !defined(AESNI) + oaes_free((OAES_CTX **) &ctx->aes_ctx); +#endif + + memcpy(ctx->state.init, ctx->text, INIT_SIZE_BYTE); + hash_permutation(&ctx->state.hs); + extra_hashes[ctx->state.hs.b[0] & 3](&ctx->state, 200, hash); +} diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 958639a0..224b06cd 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -26,8 +26,9 @@ #define CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE 600 #define CRYPTONOTE_DISPLAY_DECIMAL_POINT 8 // COIN - number of smallest units in one coin -#define COIN ((uint64_t)100000000) // pow(10, 8) -#define DEFAULT_FEE ((uint64_t)1000000) // pow(10, 6) +#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) #define DIFFICULTY_TARGET 120 // seconds diff --git a/src/cryptonote_core/blockchain_storage.cpp b/src/cryptonote_core/blockchain_storage.cpp index 5fb23fa6..c1cd4ad0 100644 --- a/src/cryptonote_core/blockchain_storage.cpp +++ b/src/cryptonote_core/blockchain_storage.cpp @@ -205,73 +205,79 @@ uint64_t blockchain_storage::get_current_blockchain_height() { return m_blocks.size(); } -bool blockchain_storage::init(const std::string& config_folder) { +bool blockchain_storage::init(const std::string& config_folder, bool load_existing) { CRITICAL_REGION_LOCAL(m_blockchain_lock); - if (!tools::create_directories_if_necessary(config_folder)) { + if (!config_folder.empty() && !tools::create_directories_if_necessary(config_folder)) { LOG_ERROR("Failed to create data directory: " << m_config_folder); return false; } m_config_folder = config_folder; - LOG_PRINT_L0("Loading blockchain..."); + if (!m_blocks.open(appendPath(config_folder, "blocks.dat"), appendPath(config_folder, "blockindexes.dat"), 1024)) { return false; } - if (m_blocks.empty()) { - const std::string filename = appendPath(m_config_folder, 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&) { - } + if (load_existing) { + LOG_PRINT_L0("Loading blockchain..."); - if (rebuild) { - LOG_PRINT_L0("No actual blockchain cache found, rebuilding internal structures..."); - std::chrono::steady_clock::time_point timePoint = std::chrono::steady_clock::now(); - for (uint32_t b = 0; b < m_blocks.size(); ++b) { - const Block& block = m_blocks[b]; - crypto::hash blockHash = get_block_hash(block.bl); - m_blockMap.insert(std::make_pair(blockHash, b)); - for (uint16_t t = 0; t < block.transactions.size(); ++t) { - const Transaction& 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 (m_blocks.empty()) { + const std::string filename = appendPath(m_config_folder, 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&) { + } + + if (rebuild) { + LOG_PRINT_L0("No actual blockchain cache found, rebuilding internal structures..."); + std::chrono::steady_clock::time_point timePoint = std::chrono::steady_clock::now(); + for (uint32_t b = 0; b < m_blocks.size(); ++b) { + const Block& block = m_blocks[b]; + crypto::hash blockHash = get_block_hash(block.bl); + m_blockMap.insert(std::make_pair(blockHash, b)); + for (uint16_t t = 0; t < block.transactions.size(); ++t) { + const Transaction& 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); + } + } + + for (uint16_t o = 0; o < transaction.tx.vout.size(); ++o) { + m_outputs[transaction.tx.vout[o].amount].push_back(std::make_pair<>(transactionIndex, o)); } } - - for (uint16_t o = 0; o < transaction.tx.vout.size(); ++o) { - m_outputs[transaction.tx.vout[o].amount].push_back(std::make_pair<>(transactionIndex, o)); - } } - } - std::chrono::duration duration = std::chrono::steady_clock::now() - timePoint; - LOG_PRINT_L0("Rebuilding internal structures took: " << duration.count()); + std::chrono::duration duration = std::chrono::steady_clock::now() - timePoint; + LOG_PRINT_L0("Rebuilding internal structures took: " << duration.count()); + } } + } else { + m_blocks.clear(); } if (m_blocks.empty()) { LOG_PRINT_L0("Blockchain not loaded, generating genesis block."); - block bl = boost::value_initialized(); + block bl = ::boost::value_initialized(); block_verification_context bvc = boost::value_initialized(); generate_genesis_block(bl); add_new_block(bl, bvc); @@ -876,7 +882,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(bei.bl, proof_of_work, bei.height); + get_block_longhash(m_cn_context, bei.bl, proof_of_work, bei.height); if (!check_hash(proof_of_work, current_diff)) { LOG_PRINT_RED_L0("Block with id: " << id << ENDL << " for alternative chain, have not enough proof of work: " << proof_of_work @@ -1497,7 +1503,7 @@ bool blockchain_storage::pushBlock(const block& blockData, block_verification_co return false; } } else { - proof_of_work = get_block_longhash(blockData, m_blocks.size()); + proof_of_work = get_block_longhash(m_cn_context, blockData, m_blocks.size()); if (!check_hash(proof_of_work, currentDifficulty)) { LOG_PRINT_L0("Block " << blockHash << ", has too weak proof of work: " << proof_of_work << ", expected difficulty: " << currentDifficulty); bvc.m_verifivation_failed = true; diff --git a/src/cryptonote_core/blockchain_storage.h b/src/cryptonote_core/blockchain_storage.h index 1cbb3dab..a64779ff 100644 --- a/src/cryptonote_core/blockchain_storage.h +++ b/src/cryptonote_core/blockchain_storage.h @@ -17,8 +17,8 @@ namespace cryptonote { 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) {}; - bool init() { return init(tools::get_default_data_dir()); } - bool init(const std::string& config_folder); + bool init() { return init(tools::get_default_data_dir(), true); } + bool init(const std::string& config_folder, bool load_existing); bool deinit(); void set_checkpoints(checkpoints&& chk_pts) { m_checkpoints = chk_pts; } @@ -75,24 +75,17 @@ namespace cryptonote { } template - bool 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) { CRITICAL_REGION_LOCAL(m_blockchain_lock); for (const auto& tx_id : txs_ids) { auto it = m_transactionMap.find(tx_id); if (it == m_transactionMap.end()) { - transaction tx; - if (!m_tx_pool.get_transaction(tx_id, tx)) { - missed_txs.push_back(tx_id); - } else { - txs.push_back(tx); - } + missed_txs.push_back(tx_id); } else { txs.push_back(transactionByIndex(it->second).tx); } } - - return true; } //debug functions @@ -146,6 +139,7 @@ namespace cryptonote { tx_memory_pool& m_tx_pool; epee::critical_section m_blockchain_lock; // TODO: add here reader/writer lock + crypto::cn_context m_cn_context; key_images_container m_spent_keys; size_t m_current_block_cumul_sz_limit; diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 2e46c99b..b2dd7ddc 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -75,27 +75,9 @@ namespace cryptonote { return m_blockchain_storage.get_blocks(start_offset, count, blocks); } //----------------------------------------------------------------------------------------------- - bool 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) { - return m_blockchain_storage.get_transactions(txs_ids, txs, missed_txs); - } - //----------------------------------------------------------------------------------------------- - bool core::get_transaction(const crypto::hash &h, transaction &tx) - { - std::vector ids; - ids.push_back(h); - std::list ltx; - std::list missing; - if (m_blockchain_storage.get_transactions(ids, ltx, missing)) - { - if (ltx.size() > 0) - { - tx = *ltx.begin(); - return true; - } - } - - return false; + m_blockchain_storage.get_transactions(txs_ids, txs, missed_txs); } //----------------------------------------------------------------------------------------------- bool core::get_alternative_blocks(std::list& blocks) @@ -108,14 +90,14 @@ namespace cryptonote return m_blockchain_storage.get_alternative_blocks_count(); } //----------------------------------------------------------------------------------------------- - bool core::init(const boost::program_options::variables_map& vm) + bool core::init(const boost::program_options::variables_map& vm, bool load_existing) { bool r = handle_command_line(vm); r = m_mempool.init(m_config_folder); CHECK_AND_ASSERT_MES(r, false, "Failed to initialize memory pool"); - r = m_blockchain_storage.init(m_config_folder); + 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); @@ -288,17 +270,17 @@ 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) - { - if(m_mempool.have_tx(tx_hash)) - { - LOG_PRINT_L2("tx " << tx_hash << "already have transaction in tx_pool"); + 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; } - if(m_blockchain_storage.have_tx(tx_hash)) - { - LOG_PRINT_L2("tx " << tx_hash << " already have transaction in blockchain"); + // It's not very good to lock on m_mempool here, because it's very hard to understand the order of locking + // tx_memory_pool::m_transactions_lock, blockchain_storage::m_blockchain_lock, and core::m_incoming_tx_lock + CRITICAL_REGION_LOCAL(m_mempool); + if (m_mempool.have_tx(tx_hash)) { + LOG_PRINT_L2("tx " << tx_hash << " is already in transaction pool"); return true; } @@ -421,30 +403,33 @@ namespace cryptonote } m_blockchain_storage.add_new_block(b, bvc); - 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() > 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 { - 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); - BOOST_FOREACH(auto& tx, txs) arg.b.txs.push_back(t_serializable_object_to_blob(tx)); - m_pprotocol->relay_block(arg, exclude_context); - } - - if (update_miner_blocktemplate) { - update_miner_block_template(); - } + if (bvc.m_added_to_main_chain && update_miner_blocktemplate) { + update_miner_block_template(); } 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() { @@ -471,9 +456,9 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- - bool core::get_pool_transactions(std::list& txs) + void core::get_pool_transactions(std::list& txs) { - return m_mempool.get_transactions(txs); + m_mempool.get_transactions(txs); } //----------------------------------------------------------------------------------------------- bool core::get_short_chain_history(std::list& ids) diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 056aa074..c74bb027 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -43,7 +43,7 @@ namespace cryptonote 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 init(const boost::program_options::variables_map& vm, bool load_existing); bool set_genesis_block(const block& b); bool deinit(); uint64_t get_current_blockchain_height(); @@ -56,8 +56,7 @@ namespace cryptonote return m_blockchain_storage.get_blocks(block_ids, blocks, missed_bs); } crypto::hash get_block_id_by_height(uint64_t height); - bool get_transactions(const std::vector& txs_ids, std::list& txs, std::list& missed_txs); - bool get_transaction(const crypto::hash &h, transaction &tx); + 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); @@ -67,7 +66,7 @@ namespace cryptonote void set_cryptonote_protocol(i_cryptonote_protocol* pprotocol); void set_checkpoints(checkpoints&& chk_pts); - bool 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); @@ -89,6 +88,7 @@ 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); diff --git a/src/cryptonote_core/cryptonote_format_utils.cpp b/src/cryptonote_core/cryptonote_format_utils.cpp index 5b6fadf2..da0e501e 100644 --- a/src/cryptonote_core/cryptonote_format_utils.cpp +++ b/src/cryptonote_core/cryptonote_format_utils.cpp @@ -81,7 +81,7 @@ namespace cryptonote block_reward += fee; std::vector out_amounts; - decompose_amount_into_digits(block_reward, DEFAULT_FEE, + 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); }); @@ -640,16 +640,16 @@ namespace cryptonote bl.minor_version = CURRENT_BLOCK_MINOR_VERSION; bl.timestamp = 0; bl.nonce = 70; - miner::find_nonce_for_given_block(bl, 1, 0); + //miner::find_nonce_for_given_block(bl, 1, 0); return true; } //--------------------------------------------------------------- - bool get_block_longhash(const block& b, crypto::hash& res, uint64_t height) + bool get_block_longhash(crypto::cn_context &context, const block& b, crypto::hash& res, uint64_t height) { blobdata bd; if(!get_block_hashing_blob(b, bd)) return false; - crypto::cn_slow_hash(bd.data(), bd.size(), res); + crypto::cn_slow_hash(context, bd.data(), bd.size(), res); return true; } //--------------------------------------------------------------- @@ -673,10 +673,10 @@ namespace cryptonote return res; } //--------------------------------------------------------------- - crypto::hash get_block_longhash(const block& b, uint64_t height) + crypto::hash get_block_longhash(crypto::cn_context &context, const block& b, uint64_t height) { crypto::hash p = null_hash; - get_block_longhash(b, p, height); + get_block_longhash(context, b, p, height); return p; } //--------------------------------------------------------------- diff --git a/src/cryptonote_core/cryptonote_format_utils.h b/src/cryptonote_core/cryptonote_format_utils.h index 662e160e..6153a046 100644 --- a/src/cryptonote_core/cryptonote_format_utils.h +++ b/src/cryptonote_core/cryptonote_format_utils.h @@ -77,8 +77,8 @@ namespace cryptonote 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(const block& b, crypto::hash& res, uint64_t height); - crypto::hash get_block_longhash(const block& b, uint64_t height); + 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); diff --git a/src/cryptonote_core/miner.cpp b/src/cryptonote_core/miner.cpp index fdc15457..88dad337 100644 --- a/src/cryptonote_core/miner.cpp +++ b/src/cryptonote_core/miner.cpp @@ -15,6 +15,8 @@ #include "cryptonote_format_utils.h" #include "file_io_utils.h" #include "common/command_line.h" +#include "crypto/hash.h" +#include "crypto/random.h" #include "string_coding.h" #include "storages/portable_storage_template_helper.h" @@ -42,9 +44,9 @@ namespace cryptonote m_thread_index(0), m_phandler(phandler), m_height(0), - m_pausers_count(0), + m_pausers_count(0), m_threads_total(0), - m_starter_nonce(0), + m_starter_nonce(0), m_last_hr_merge_time(0), m_hashes(0), m_do_print_hashrate(false), @@ -83,7 +85,7 @@ namespace cryptonote block bl = AUTO_VAL_INIT(bl); difficulty_type di = AUTO_VAL_INIT(di); uint64_t height = AUTO_VAL_INIT(height); - cryptonote::blobdata extra_nonce; + cryptonote::blobdata extra_nonce; if(m_extra_messages.size() && m_config.current_extra_message_index < m_extra_messages.size()) { extra_nonce = m_extra_messages[m_config.current_extra_message_index]; @@ -109,7 +111,7 @@ namespace cryptonote merge_hr(); return true; }); - + return true; } //----------------------------------------------------------------------------------------------------- @@ -192,7 +194,7 @@ namespace cryptonote { return !m_stop; } - //----------------------------------------------------------------------------------------------------- + //----------------------------------------------------------------------------------------------------- bool miner::start(const account_public_address& adr, size_t threads_count, const boost::thread::attributes& attrs) { m_mine_address = adr; @@ -252,12 +254,12 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------------- - bool miner::find_nonce_for_given_block(block& bl, const difficulty_type& diffic, uint64_t height) + 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(bl, h, height); + get_block_longhash(context, bl, h, height); if(check_hash(h, diffic)) { @@ -308,6 +310,7 @@ namespace cryptonote uint64_t height = 0; difficulty_type local_diff = 0; uint32_t local_template_ver = 0; + crypto::cn_context context; block b; while(!m_stop) { @@ -319,7 +322,7 @@ namespace cryptonote if(local_template_ver != m_template_no) { - + CRITICAL_REGION_BEGIN(m_template_lock); b = m_template; local_diff = m_diffic; @@ -338,7 +341,7 @@ namespace cryptonote b.nonce = nonce; crypto::hash h; - get_block_longhash(b, h, height); + get_block_longhash(context, b, h, height); if(!m_stop && check_hash(h, local_diff)) { diff --git a/src/cryptonote_core/miner.h b/src/cryptonote_core/miner.h index 61c063dd..28121e66 100644 --- a/src/cryptonote_core/miner.h +++ b/src/cryptonote_core/miner.h @@ -2,7 +2,7 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#pragma once +#pragma once #include #include @@ -27,7 +27,7 @@ namespace cryptonote /************************************************************************/ class miner { - public: + public: miner(i_miner_handler* phandler); ~miner(); bool init(const boost::program_options::variables_map& vm); @@ -42,7 +42,7 @@ namespace cryptonote bool on_idle(); void on_synchronized(); //synchronous analog (for fast calls) - static bool find_nonce_for_given_block(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, uint64_t height); void pause(); void resume(); void do_print_hashrate(bool do_hr); @@ -51,7 +51,7 @@ namespace cryptonote bool worker_thread(); bool request_block_template(); void merge_hr(); - + struct miner_config { uint64_t current_extra_message_index; @@ -69,7 +69,7 @@ namespace cryptonote std::atomic m_starter_nonce; difficulty_type m_diffic; uint64_t m_height; - volatile uint32_t m_thread_index; + volatile uint32_t m_thread_index; volatile uint32_t m_threads_total; std::atomic m_pausers_count; epee::critical_section m_miners_count_lock; @@ -82,7 +82,7 @@ namespace cryptonote epee::math_helper::once_a_time_seconds<2> m_update_merge_hr_interval; std::vector m_extra_messages; miner_config m_config; - std::string m_config_folder_path; + std::string m_config_folder_path; std::atomic m_last_hr_merge_time; std::atomic m_hashes; std::atomic m_current_hash_rate; diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 24e5752a..7fab0d2e 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -20,60 +20,53 @@ DISABLE_VS_WARNINGS(4244 4345 4503) //'boost::foreach_detail_::or_' : decorated name length exceeded, name was truncated -namespace cryptonote -{ +namespace cryptonote { //--------------------------------------------------------------------------------- - tx_memory_pool::tx_memory_pool(blockchain_storage& bchs): m_blockchain(bchs) - { - + tx_memory_pool::tx_memory_pool(blockchain_storage& bchs): m_blockchain(bchs) { } //--------------------------------------------------------------------------------- - 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) - { - - - if(!check_inputs_types_supported(tx)) - { + 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) { + if (!check_inputs_types_supported(tx)) { tvc.m_verifivation_failed = true; return false; } uint64_t inputs_amount = 0; - if(!get_inputs_money_amount(tx, inputs_amount)) - { + if (!get_inputs_money_amount(tx, inputs_amount)) { tvc.m_verifivation_failed = true; return false; } 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 " << outputs_amount << ", have " << inputs_amount); + 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)); + 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; } //check key images for transaction if it is not kept by block - if(!kept_by_block) - { - if(have_tx_keyimges_as_spent(tx)) - { + if (!kept_by_block) { + if (have_tx_keyimges_as_spent(tx)) { LOG_ERROR("Transaction with id= "<< id << " used already spent key images"); 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) - { + 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"); @@ -85,14 +78,12 @@ namespace cryptonote txd_p.first->second.kept_by_block = kept_by_block; tvc.m_verifivation_impossible = true; tvc.m_added_to_pool = true; - }else - { + } else { LOG_PRINT_L0("tx used wrong inputs, rejected"); tvc.m_verifivation_failed = true; return false; } - }else - { + } 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"); @@ -106,14 +97,14 @@ namespace cryptonote txd_p.first->second.last_failed_id = null_hash; tvc.m_added_to_pool = true; - if(txd_p.first->second.fee > 0) + if (txd_p.first->second.fee > 0) { tvc.m_should_be_relayed = true; + } } tvc.m_verifivation_failed = true; //update image_keys container, here should everything goes ok. - BOOST_FOREACH(const auto& in, tx.vin) - { + 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 @@ -128,47 +119,43 @@ namespace cryptonote 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); } //--------------------------------------------------------------------------------- - bool tx_memory_pool::remove_transaction_keyimages(const transaction& tx) - { + bool tx_memory_pool::remove_transaction_keyimages(const transaction& tx) { CRITICAL_REGION_LOCAL(m_transactions_lock); - BOOST_FOREACH(const txin_v& vi, tx.vin) - { + 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 << ENDL - << "transaction id = " << get_transaction_hash(tx)); - std::unordered_set& key_image_set = it->second; - CHECK_AND_ASSERT_MES(key_image_set.size(), false, "empty key_image set, img=" << txin.k_image << ENDL - << "transaction id = " << get_transaction_hash(tx)); + 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(get_transaction_hash(tx)); - CHECK_AND_ASSERT_MES(key_image_set.size(), false, "transaction id not found in key_image set, img=" << txin.k_image << ENDL - << "transaction id = " << get_transaction_hash(tx)); + 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.size()) - { + 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& blob_size, uint64_t& fee) { CRITICAL_REGION_LOCAL(m_transactions_lock); auto it = m_transactions.find(id); - if(it == m_transactions.end()) + if (it == m_transactions.end()) { return false; + } tx = it->second.tx; blob_size = it->second.blob_size; @@ -178,134 +165,111 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- - size_t tx_memory_pool::get_transactions_count() - { + size_t tx_memory_pool::get_transactions_count() const { CRITICAL_REGION_LOCAL(m_transactions_lock); return m_transactions.size(); } //--------------------------------------------------------------------------------- - bool tx_memory_pool::get_transactions(std::list& txs) - { + void tx_memory_pool::get_transactions(std::list& txs) const { CRITICAL_REGION_LOCAL(m_transactions_lock); - BOOST_FOREACH(const auto& tx_vt, m_transactions) + for (const auto& tx_vt : m_transactions) { txs.push_back(tx_vt.second.tx); - + } + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::on_blockchain_inc(uint64_t new_block_height, const crypto::hash& top_block_id) { return true; } //--------------------------------------------------------------------------------- - bool tx_memory_pool::get_transaction(const crypto::hash& id, transaction& tx) - { + bool tx_memory_pool::on_blockchain_dec(uint64_t new_block_height, const crypto::hash& top_block_id) { + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::have_tx(const crypto::hash &id) const { CRITICAL_REGION_LOCAL(m_transactions_lock); - auto it = m_transactions.find(id); - if(it == m_transactions.end()) - return false; - tx = it->second.tx; - return true; - } - //--------------------------------------------------------------------------------- - bool tx_memory_pool::on_blockchain_inc(uint64_t new_block_height, const crypto::hash& top_block_id) - { - return true; - } - //--------------------------------------------------------------------------------- - bool tx_memory_pool::on_blockchain_dec(uint64_t new_block_height, const crypto::hash& top_block_id) - { - return true; - } - //--------------------------------------------------------------------------------- - bool tx_memory_pool::have_tx(const crypto::hash &id) - { - CRITICAL_REGION_LOCAL(m_transactions_lock); - if(m_transactions.count(id)) + if (m_transactions.count(id)) { return true; - return false; - } - //--------------------------------------------------------------------------------- - bool tx_memory_pool::have_tx_keyimges_as_spent(const transaction& tx) - { - CRITICAL_REGION_LOCAL(m_transactions_lock); - BOOST_FOREACH(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) - { + 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() - { + void tx_memory_pool::lock() const { m_transactions_lock.lock(); } //--------------------------------------------------------------------------------- - void tx_memory_pool::unlock() - { + void tx_memory_pool::unlock() const { m_transactions_lock.unlock(); } //--------------------------------------------------------------------------------- - bool tx_memory_pool::is_transaction_ready_to_go(tx_details& txd) - { + 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)) + 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 + } - if(!m_blockchain.check_tx_inputs(txd.tx, txd.max_used_block_height, txd.max_used_block_id)) - { + 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()) + } 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 (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)) + 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)) - { + 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 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_blockchain.have_tx_keyimges_as_spent(txd.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++) - { + 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)) + 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++) - { + 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); @@ -313,57 +277,43 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- - std::string tx_memory_pool::print_pool(bool short_format) - { + std::string tx_memory_pool::print_pool(bool short_format) const { std::stringstream ss; CRITICAL_REGION_LOCAL(m_transactions_lock); - BOOST_FOREACH(transactions_container::value_type& txe, m_transactions) - { - if(short_format) - { - tx_details& txd = txe.second; - ss << "id: " << txe.first << ENDL - << "blob_size: " << txd.blob_size << ENDL - << "fee: " << txd.fee << ENDL - << "kept_by_block: " << txd.kept_by_block << ENDL - << "max_used_block_height: " << txd.max_used_block_height << ENDL - << "max_used_block_id: " << txd.max_used_block_id << ENDL - << "last_failed_height: " << txd.last_failed_height << ENDL - << "last_failed_id: " << txd.last_failed_id << ENDL; - }else - { - tx_details& txd = txe.second; - ss << "id: " << txe.first << ENDL - << obj_to_json_str(txd.tx) << ENDL - << "blob_size: " << txd.blob_size << ENDL - << "fee: " << txd.fee << ENDL - << "kept_by_block: " << txd.kept_by_block << ENDL - << "max_used_block_height: " << txd.max_used_block_height << ENDL - << "max_used_block_id: " << txd.max_used_block_id << ENDL - << "last_failed_height: " << txd.last_failed_height << ENDL - << "last_failed_id: " << txd.last_failed_id << ENDL; + for (const transactions_container::value_type& txe : m_transactions) { + const tx_details& txd = txe.second; + ss << "id: " << txe.first << std::endl; + if (!short_format) { + ss << obj_to_json_str(*const_cast(&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; } + 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, 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 = 2 * median_size - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + size_t max_total_size = (125 * median_size) / 100 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; std::unordered_set k_images; - BOOST_FOREACH(transactions_container::value_type& tx, m_transactions) - { - if (max_total_size < total_size + tx.second.blob_size) + for (transactions_container::value_type& tx : m_transactions) { + if (max_total_size < total_size + tx.second.blob_size) { continue; + } - if (!is_transaction_ready_to_go(tx.second) || have_key_images(k_images, tx.second.tx)) + if (!is_transaction_ready_to_go(tx.second) || have_key_images(k_images, tx.second.tx)) { continue; + } bl.tx_hashes.push_back(tx.first); total_size += tx.second.blob_size; @@ -374,34 +324,35 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- - bool tx_memory_pool::init(const std::string& config_folder) - { + bool tx_memory_pool::init(const std::string& config_folder) { + CRITICAL_REGION_LOCAL(m_transactions_lock); + m_config_folder = config_folder; std::string state_file_path = config_folder + "/" + CRYPTONOTE_POOLDATA_FILENAME; boost::system::error_code ec; - if(!boost::filesystem::exists(state_file_path, ec)) + if (!boost::filesystem::exists(state_file_path, ec)) { return true; - bool res = tools::unserialize_obj_from_file(*this, state_file_path); - if(!res) - { - LOG_PRINT_L0("Failed to load memory pool from file " << state_file_path); } - return res; - } + bool res = tools::unserialize_obj_from_file(*this, state_file_path); + if (!res) { + LOG_ERROR("Failed to load memory pool from file " << state_file_path); + m_transactions.clear(); + m_spent_key_images.clear(); + } + // Ignore deserialization error + return true; + } //--------------------------------------------------------------------------------- - bool tx_memory_pool::deinit() - { - if (!tools::create_directories_if_necessary(m_config_folder)) - { + bool tx_memory_pool::deinit() { + if (!tools::create_directories_if_necessary(m_config_folder)) { LOG_PRINT_L0("Failed to create data directory: " << m_config_folder); return false; } std::string state_file_path = m_config_folder + "/" + CRYPTONOTE_POOLDATA_FILENAME; bool res = tools::serialize_obj_to_file(*this, state_file_path); - if(!res) - { + if (!res) { LOG_PRINT_L0("Failed to serialize memory pool to file " << state_file_path); } return true; diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 26d273aa..30b9061f 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -18,61 +18,52 @@ #include "crypto/hash.h" -namespace cryptonote -{ +namespace cryptonote { class blockchain_storage; + /************************************************************************/ /* */ /************************************************************************/ - - class tx_memory_pool: boost::noncopyable - { + class tx_memory_pool: boost::noncopyable { public: tx_memory_pool(blockchain_storage& bchs); + + // 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); //gets tx and remove it from pool bool take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee); - bool have_tx(const crypto::hash &id); - bool have_tx_keyimg_as_spent(const crypto::key_image& key_im); - bool have_tx_keyimges_as_spent(const transaction& tx); - 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); - void lock(); - void unlock(); + void lock() const; + void unlock() const; - // load/store operations - bool init(const std::string& config_folder); - bool deinit(); bool fill_block_template(block &bl, size_t median_size, uint64_t already_generated_coins, size_t &total_size, uint64_t &fee); - bool get_transactions(std::list& txs); - bool get_transaction(const crypto::hash& h, transaction& tx); - size_t get_transactions_count(); - bool remove_transaction_keyimages(const transaction& tx); - bool have_key_images(const std::unordered_set& kic, const transaction& tx); - bool append_key_images(std::unordered_set& kic, const transaction& tx); - std::string print_pool(bool short_format); - /*bool flush_pool(const std::strig& folder); - bool inflate_pool(const std::strig& folder);*/ + void get_transactions(std::list& txs) const; + size_t get_transactions_count() const; + std::string print_pool(bool short_format) const; #define CURRENT_MEMPOOL_ARCHIVE_VER 7 template - void serialize(archive_t & a, const unsigned int version) - { - if(version < CURRENT_MEMPOOL_ARCHIVE_VER ) + void serialize(archive_t & a, const unsigned int version) { + if (version < CURRENT_MEMPOOL_ARCHIVE_VER) { return; + } + CRITICAL_REGION_LOCAL(m_transactions_lock); a & m_transactions; a & m_spent_key_images; } - struct tx_details - { + struct tx_details { transaction tx; size_t blob_size; uint64_t fee; @@ -85,58 +76,40 @@ namespace cryptonote }; private: - bool is_transaction_ready_to_go(tx_details& txd); + 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; + typedef std::unordered_map transactions_container; typedef std::unordered_map > key_images_container; - epee::critical_section m_transactions_lock; + mutable epee::critical_section m_transactions_lock; transactions_container m_transactions; key_images_container m_spent_key_images; - //transactions_container m_alternative_transactions; - std::string m_config_folder; blockchain_storage& m_blockchain; + /************************************************************************/ /* */ /************************************************************************/ - /*class inputs_visitor: public boost::static_visitor - { - key_images_container& m_spent_keys; + class amount_visitor: public boost::static_visitor { public: - inputs_visitor(key_images_container& spent_keys): m_spent_keys(spent_keys) - {} - bool operator()(const txin_to_key& tx) const - { - auto pr = m_spent_keys.insert(tx.k_image); - CHECK_AND_ASSERT_MES(pr.second, false, "Tried to insert transaction with input seems already spent, input: " << epee::string_tools::pod_to_hex(tx.k_image)); - return true; - } - bool operator()(const txin_gen& tx) const - { - CHECK_AND_ASSERT_MES(false, false, "coinbase transaction in memory pool"); - return false; - } - bool operator()(const txin_to_script& tx) const {return false;} - bool operator()(const txin_to_scripthash& tx) const {return false;} - }; */ - /************************************************************************/ - /* */ - /************************************************************************/ - class amount_visitor: public boost::static_visitor - { - public: - uint64_t operator()(const txin_to_key& tx) const - { + uint64_t operator()(const txin_to_key& tx) const { return tx.amount; } - uint64_t operator()(const txin_gen& tx) const - { + + 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;} + + uint64_t operator()(const txin_to_script& tx) const { return 0; } + uint64_t operator()(const txin_to_scripthash& tx) const { return 0; } }; #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) @@ -145,13 +118,10 @@ namespace cryptonote }; } -namespace boost -{ - namespace serialization - { +namespace boost { + namespace serialization { template - void serialize(archive_t & ar, cryptonote::tx_memory_pool::tx_details& td, const unsigned int version) - { + void serialize(archive_t & ar, cryptonote::tx_memory_pool::tx_details& td, const unsigned int version) { ar & td.blob_size; ar & td.fee; ar & td.tx; @@ -159,11 +129,8 @@ namespace boost ar & td.max_used_block_id; ar & td.last_failed_height; ar & td.last_failed_id; - } } } + BOOST_CLASS_VERSION(cryptonote::tx_memory_pool, CURRENT_MEMPOOL_ARCHIVE_VER) - - - diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 329f44e7..f1f9171c 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -160,7 +160,7 @@ int main(int argc, char* argv[]) //initialize core here LOG_PRINT_L0("Initializing core..."); - res = ccore.init(vm); + res = ccore.init(vm, true); CHECK_AND_ASSERT_MES(res, 1, "Failed to initialize core"); LOG_PRINT_L0("Core initialized OK"); diff --git a/src/miner/simpleminer.cpp b/src/miner/simpleminer.cpp index a1c1ad5c..e03f6085 100644 --- a/src/miner/simpleminer.cpp +++ b/src/miner/simpleminer.cpp @@ -101,6 +101,7 @@ namespace mining std::string pool_session_id; simpleminer::job_details_native job = AUTO_VAL_INIT(job); uint64_t last_job_ticks = 0; + crypto::cn_context context; while(true) { @@ -158,11 +159,11 @@ namespace mining //uint32_t c = (*((uint32_t*)&job.blob.data()[39])); ++(*((uint32_t*)&job.blob.data()[39])); crypto::hash h = cryptonote::null_hash; - crypto::cn_slow_hash(job.blob.data(), job.blob.size(), h); + crypto::cn_slow_hash(context, job.blob.data(), job.blob.size(), h); if( ((uint32_t*)&h)[7] < job.target ) { //found! - + COMMAND_RPC_SUBMITSHARE::request submit_request = AUTO_VAL_INIT(submit_request); COMMAND_RPC_SUBMITSHARE::response submit_response = AUTO_VAL_INIT(submit_response); submit_request.id = pool_session_id; diff --git a/src/node_rpc_proxy/InitState.h b/src/node_rpc_proxy/InitState.h new file mode 100644 index 00000000..13c8aad6 --- /dev/null +++ b/src/node_rpc_proxy/InitState.h @@ -0,0 +1,83 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include + +#include "include_base_utils.h" + +namespace tools { + +class InitState { +public: + InitState() : m_state(STATE_NOT_INITIALIZED) { + } + + bool initialized() const volatile { + return STATE_INITIALIZED == m_state.load(std::memory_order_acquire); + } + + bool beginInit() volatile { + State state = STATE_NOT_INITIALIZED; + if (!m_state.compare_exchange_strong(state, STATE_INITIALIZING, std::memory_order_seq_cst)) { + LOG_ERROR("object has been already initialized"); + return false; + } + return true; + } + + bool endInit() volatile { + State expectedState = STATE_INITIALIZING; + if (!m_state.compare_exchange_strong(expectedState, STATE_INITIALIZED, std::memory_order_seq_cst)) { + LOG_ERROR("Unexpected state: " << expectedState); + return false; + } + return true; + } + + bool beginShutdown() volatile { + while (true) { + State state = m_state.load(std::memory_order_relaxed); + if (STATE_NOT_INITIALIZED == state) { + return true; + } else if (STATE_INITIALIZING == state) { + LOG_ERROR("Object is being initialized"); + return false; + } else if (STATE_INITIALIZED == state) { + if (m_state.compare_exchange_strong(state, STATE_SHUTTING_DOWN, std::memory_order_seq_cst)) { + return true; + } + } else if (STATE_SHUTTING_DOWN == state) { + LOG_ERROR("Object is being shutting down"); + return false; + } else { + LOG_ERROR("Unknown state " << state); + return false; + } + } + } + + bool endShutdown() volatile { + State expectedState = STATE_SHUTTING_DOWN; + if (!m_state.compare_exchange_strong(expectedState, STATE_NOT_INITIALIZED, std::memory_order_seq_cst)) { + LOG_ERROR("Unexpected state: " << expectedState); + return false; + } + return true; + } + +private: + enum State { + STATE_NOT_INITIALIZED, + STATE_INITIALIZING, + STATE_INITIALIZED, + STATE_SHUTTING_DOWN + }; + +private: + std::atomic m_state; +}; + +} diff --git a/src/node_rpc_proxy/NodeErrors.cpp b/src/node_rpc_proxy/NodeErrors.cpp new file mode 100644 index 00000000..7558f62a --- /dev/null +++ b/src/node_rpc_proxy/NodeErrors.cpp @@ -0,0 +1,13 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "NodeErrors.h" + +namespace cryptonote { +namespace error { + +NodeErrorCategory NodeErrorCategory::INSTANCE; + +} +} diff --git a/src/node_rpc_proxy/NodeErrors.h b/src/node_rpc_proxy/NodeErrors.h new file mode 100644 index 00000000..0f21f611 --- /dev/null +++ b/src/node_rpc_proxy/NodeErrors.h @@ -0,0 +1,56 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include +#include + +namespace cryptonote { +namespace error { + +// custom error conditions enum type: +enum NodeErrorCodes { + NOT_INITIALIZED = 1, + ALREADY_INITIALIZED, + NETWORK_ERROR, + NODE_BUSY, + INTERNAL_NODE_ERROR, +}; + +// custom category: +class NodeErrorCategory : public std::error_category { +public: + static NodeErrorCategory INSTANCE; + + virtual const char* name() const throw() { + return "NodeErrorCategory"; + } + + virtual std::error_condition default_error_condition(int ev) const throw() { + return std::error_condition(ev, *this); + } + + virtual std::string message(int ev) const { + switch (ev) { + case NOT_INITIALIZED: return "Object was not initialized"; + case ALREADY_INITIALIZED: return "Object has been already initialized"; + case NETWORK_ERROR: return "Network error"; + case NODE_BUSY: return "Node is busy"; + case INTERNAL_NODE_ERROR: return "Internal node error"; + default: return "Unknown error"; + } + } + +private: + NodeErrorCategory() { + } +}; + +} +} + +inline std::error_code make_error_code(cryptonote::error::NodeErrorCodes e) { + return std::error_code(static_cast(e), cryptonote::error::NodeErrorCategory::INSTANCE); +} diff --git a/src/node_rpc_proxy/NodeRpcProxy.cpp b/src/node_rpc_proxy/NodeRpcProxy.cpp new file mode 100644 index 00000000..3effca9d --- /dev/null +++ b/src/node_rpc_proxy/NodeRpcProxy.cpp @@ -0,0 +1,255 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "NodeRpcProxy.h" + +#include +#include +#include + +#include "cryptonote_core/cryptonote_format_utils.h" +#include "rpc/core_rpc_server_commands_defs.h" +#include "storages/http_abstract_invoke.h" +#include "NodeErrors.h" + +namespace cryptonote { + +using namespace CryptoNote; + +namespace { + std::error_code interpretJsonRpcResponse(bool ok, const std::string& status) { + if (!ok) { + return make_error_code(error::NETWORK_ERROR); + } else if (CORE_RPC_STATUS_BUSY == status) { + return make_error_code(error::NODE_BUSY); + } else if (CORE_RPC_STATUS_OK != status) { + return make_error_code(error::INTERNAL_NODE_ERROR); + } + return std::error_code(); + } +} + +NodeRpcProxy::NodeRpcProxy(const std::string& nodeHost, unsigned short nodePort) + : m_nodeAddress("http://" + nodeHost + ":" + std::to_string(nodePort)) + , m_rpcTimeout(10000) + , m_pullTimer(m_ioService) + , m_pullInterval(10000) { + resetInternalState(); +} + +NodeRpcProxy::~NodeRpcProxy() { + shutdown(); +} + +void NodeRpcProxy::resetInternalState() { + m_ioService.reset(); + m_observerManager.clear(); + + m_peerCount = 0; + m_nodeHeight = 0; + m_networkHeight = 0; + m_lastKnowHash = cryptonote::null_hash; +} + +void NodeRpcProxy::init(const INode::Callback& callback) { + if (!m_initState.beginInit()) { + callback(make_error_code(error::ALREADY_INITIALIZED)); + return; + } + + resetInternalState(); + m_workerThread = std::thread(std::bind(&NodeRpcProxy::workerThread, this, callback)); +} + +bool NodeRpcProxy::shutdown() { + if (!m_initState.beginShutdown()) { + return false; + } + + boost::system::error_code ignored_ec; + m_pullTimer.cancel(ignored_ec); + m_ioService.stop(); + m_workerThread.join(); + + m_initState.endShutdown(); + return true; +} + +void NodeRpcProxy::workerThread(const INode::Callback& initialized_callback) { + if (!m_initState.endInit()) { + return; + } + + initialized_callback(std::error_code()); + + pullNodeStatusAndScheduleTheNext(); + + while (!m_ioService.stopped()) { + m_ioService.run_one(); + } +} + +void NodeRpcProxy::pullNodeStatusAndScheduleTheNext() { + updateNodeStatus(); + + m_pullTimer.expires_from_now(boost::posix_time::milliseconds(m_pullInterval)); + m_pullTimer.async_wait([=](const boost::system::error_code& ec) { + if (ec != boost::asio::error::operation_aborted) { + pullNodeStatusAndScheduleTheNext(); + } + }); +} + +void NodeRpcProxy::updateNodeStatus() { + cryptonote::COMMAND_RPC_GET_LAST_BLOCK_HEADER::request req = AUTO_VAL_INIT(req); + cryptonote::COMMAND_RPC_GET_LAST_BLOCK_HEADER::response rsp = AUTO_VAL_INIT(rsp); + bool r = epee::net_utils::invoke_http_json_rpc(m_nodeAddress + "/json_rpc", "getlastblockheader", req, rsp, m_httpClient, m_rpcTimeout); + std::error_code ec = interpretJsonRpcResponse(r, rsp.status); + if (!ec) { + crypto::hash blockHash; + if (!parse_hash256(rsp.block_header.hash, blockHash)) { + LOG_ERROR("Invalid block hash format: " << rsp.block_header.hash); + return; + } + + if (blockHash != m_lastKnowHash) { + m_lastKnowHash = blockHash; + m_nodeHeight = rsp.block_header.height; + // TODO request and update network height + m_networkHeight = m_nodeHeight; + m_observerManager.notify(&INodeObserver::lastKnownBlockHeightUpdated, m_networkHeight); + //if (m_networkHeight != rsp.block_header.network_height) { + // m_networkHeight = rsp.block_header.network_height; + // m_observerManager.notify(&INodeObserver::lastKnownBlockHeightUpdated, m_networkHeight); + //} + m_observerManager.notify(&INodeObserver::localBlockchainUpdated, m_nodeHeight); + } + } else { + LOG_PRINT_L2("Failed to invoke getlastblockheader: " << ec.message() << ':' << ec.value()); + } + + updatePeerCount(); +} + +void NodeRpcProxy::updatePeerCount() { + cryptonote::COMMAND_RPC_GET_INFO::request req = AUTO_VAL_INIT(req); + cryptonote::COMMAND_RPC_GET_INFO::response rsp = AUTO_VAL_INIT(rsp); + bool r = epee::net_utils::invoke_http_json_remote_command2(m_nodeAddress + "/getinfo", req, rsp, m_httpClient, m_rpcTimeout); + std::error_code ec = interpretJsonRpcResponse(r, rsp.status); + if (!ec) { + size_t peerCount = rsp.incoming_connections_count + rsp.outgoing_connections_count; + if (peerCount != m_peerCount) { + m_peerCount = peerCount; + m_observerManager.notify(&INodeObserver::peerCountUpdated, m_peerCount); + } + } else { + LOG_PRINT_L2("Failed to invoke getinfo: " << ec.message() << ':' << ec.value()); + } +} + +bool NodeRpcProxy::addObserver(INodeObserver* observer) { + return m_observerManager.add(observer); +} + +bool NodeRpcProxy::removeObserver(INodeObserver* observer) { + return m_observerManager.remove(observer); +} + +size_t NodeRpcProxy::getPeerCount() const { + return m_peerCount; +} + +uint64_t NodeRpcProxy::getLastLocalBlockHeight() const { + return m_nodeHeight; +} + +uint64_t NodeRpcProxy::getLastKnownBlockHeight() const { + return m_networkHeight; +} + +void NodeRpcProxy::relayTransaction(const cryptonote::transaction& transaction, const Callback& callback) { + if (!m_initState.initialized()) { + callback(make_error_code(error::NOT_INITIALIZED)); + return; + } + + // TODO: m_ioService.stop() won't inkove callback(aborted). Fix it + m_ioService.post(std::bind(&NodeRpcProxy::doRelayTransaction, this, transaction, callback)); +} + +void NodeRpcProxy::getRandomOutsByAmounts(std::vector&& amounts, uint64_t outsCount, std::vector& outs, const Callback& callback) { + if (!m_initState.initialized()) { + callback(make_error_code(error::NOT_INITIALIZED)); + return; + } + + m_ioService.post(std::bind(&NodeRpcProxy::doGetRandomOutsByAmounts, this, std::move(amounts), outsCount, std::ref(outs), callback)); +} + +void NodeRpcProxy::getNewBlocks(std::list&& knownBlockIds, std::list& newBlocks, uint64_t& startHeight, const Callback& callback) { + if (!m_initState.initialized()) { + callback(make_error_code(error::NOT_INITIALIZED)); + return; + } + + m_ioService.post(std::bind(&NodeRpcProxy::doGetNewBlocks, this, std::move(knownBlockIds), std::ref(newBlocks), std::ref(startHeight), callback)); +} + +void NodeRpcProxy::getTransactionOutsGlobalIndices(const crypto::hash& transactionHash, std::vector& outsGlobalIndices, const Callback& callback) { + if (!m_initState.initialized()) { + callback(make_error_code(error::NOT_INITIALIZED)); + return; + } + + m_ioService.post(std::bind(&NodeRpcProxy::doGetTransactionOutsGlobalIndices, this, transactionHash, std::ref(outsGlobalIndices), 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)); + bool r = epee::net_utils::invoke_http_json_remote_command2(m_nodeAddress + "/sendrawtransaction", req, rsp, m_httpClient, m_rpcTimeout); + std::error_code ec = interpretJsonRpcResponse(r, rsp.status); + callback(ec); +} + +void NodeRpcProxy::doGetRandomOutsByAmounts(std::vector& amounts, uint64_t outsCount, std::vector& outs, const Callback& callback) { + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request req = AUTO_VAL_INIT(req); + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response rsp = AUTO_VAL_INIT(rsp); + req.amounts = std::move(amounts); + req.outs_count = outsCount; + bool r = epee::net_utils::invoke_http_bin_remote_command2(m_nodeAddress + "/getrandom_outs.bin", req, rsp, m_httpClient, m_rpcTimeout); + std::error_code ec = interpretJsonRpcResponse(r, rsp.status); + if (!ec) { + outs = std::move(rsp.outs); + } + callback(ec); +} + +void NodeRpcProxy::doGetNewBlocks(std::list& knownBlockIds, std::list& newBlocks, uint64_t& startHeight, const Callback& callback) { + cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::request req = AUTO_VAL_INIT(req); + cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::response rsp = AUTO_VAL_INIT(rsp); + req.block_ids = std::move(knownBlockIds); + bool r = epee::net_utils::invoke_http_bin_remote_command2(m_nodeAddress + "/getblocks.bin", req, rsp, m_httpClient, m_rpcTimeout); + std::error_code ec = interpretJsonRpcResponse(r, rsp.status); + if (!ec) { + newBlocks = std::move(rsp.blocks); + startHeight = rsp.start_height; + } + callback(ec); +} + +void NodeRpcProxy::doGetTransactionOutsGlobalIndices(const crypto::hash& transactionHash, std::vector& outsGlobalIndices, const Callback& callback) { + cryptonote::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request req = AUTO_VAL_INIT(req); + cryptonote::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response rsp = AUTO_VAL_INIT(rsp); + req.txid = transactionHash; + bool r = epee::net_utils::invoke_http_bin_remote_command2(m_nodeAddress + "/get_o_indexes.bin", req, rsp, m_httpClient, m_rpcTimeout); + std::error_code ec = interpretJsonRpcResponse(r, rsp.status); + if (!ec) { + outsGlobalIndices = std::move(rsp.o_indexes); + } + callback(ec); +} + +} diff --git a/src/node_rpc_proxy/NodeRpcProxy.h b/src/node_rpc_proxy/NodeRpcProxy.h new file mode 100644 index 00000000..adf1f30f --- /dev/null +++ b/src/node_rpc_proxy/NodeRpcProxy.h @@ -0,0 +1,76 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include +#include + +#include + +#include "common/ObserverManager.h" +#include "include_base_utils.h" +#include "net/http_client.h" +#include "InitState.h" +#include "INode.h" + +namespace cryptonote { + +class NodeRpcProxy : public CryptoNote::INode { +public: + NodeRpcProxy(const std::string& nodeHost, unsigned short nodePort); + virtual ~NodeRpcProxy(); + + virtual bool addObserver(CryptoNote::INodeObserver* observer); + virtual bool removeObserver(CryptoNote::INodeObserver* observer); + + virtual void init(const Callback& callback); + virtual bool shutdown(); + + virtual size_t getPeerCount() const; + virtual uint64_t getLastLocalBlockHeight() const; + virtual uint64_t getLastKnownBlockHeight() const; + + 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); + + unsigned int rpcTimeout() const { return m_rpcTimeout; } + void rpcTimeout(unsigned int val) { m_rpcTimeout = val; } + +private: + void resetInternalState(); + void workerThread(const Callback& initialized_callback); + + void pullNodeStatusAndScheduleTheNext(); + void updateNodeStatus(); + void updatePeerCount(); + + 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); + +private: + tools::InitState m_initState; + std::thread m_workerThread; + boost::asio::io_service m_ioService; + tools::ObserverManager m_observerManager; + + std::string m_nodeAddress; + unsigned int m_rpcTimeout; + epee::net_utils::http::http_simple_client m_httpClient; + + boost::asio::deadline_timer m_pullTimer; + uint64_t m_pullInterval; + + // Internal state + size_t m_peerCount; + uint64_t m_nodeHeight; + uint64_t m_networkHeight; + crypto::hash m_lastKnowHash; +}; + +} diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 07f4a1cb..4815af2a 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -175,12 +175,7 @@ namespace cryptonote } std::list missed_txs; std::list txs; - bool r = m_core.get_transactions(vh, txs, missed_txs); - if(!r) - { - res.status = "Failed"; - return true; - } + m_core.get_transactions(vh, txs, missed_txs); BOOST_FOREACH(auto& tx, txs) { @@ -415,12 +410,17 @@ namespace cryptonote } cryptonote::block_verification_context bvc = AUTO_VAL_INIT(bvc); m_core.handle_incoming_block(blockblob, bvc); - if(!bvc.m_added_to_main_chain) - { + 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 { error_resp.code = CORE_RPC_ERROR_CODE_BLOCK_NOT_ACCEPTED; error_resp.message = "Block not accepted"; return false; } + res.status = CORE_RPC_STATUS_OK; return true; } diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index d67ebfdd..7a957319 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -115,8 +115,8 @@ namespace cryptonote { struct request { - std::list amounts; - uint64_t outs_count; + std::vector amounts; + uint64_t outs_count; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(amounts) KV_SERIALIZE(outs_count) @@ -363,7 +363,11 @@ namespace cryptonote struct COMMAND_RPC_GET_LAST_BLOCK_HEADER { - typedef std::list request; + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; struct response { diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 4c6a7360..51e80bdd 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -64,19 +64,6 @@ namespace return err; } - bool parse_payment_id(const std::string& payment_id_str, crypto::hash& payment_id) - { - blobdata payment_id_data; - if(!string_tools::parse_hexstr_to_binbuff(payment_id_str, payment_id_data)) - return false; - - if(sizeof(crypto::hash) != payment_id_data.size()) - return false; - - payment_id = *reinterpret_cast(payment_id_data.data()); - return true; - } - class message_writer { public: @@ -448,9 +435,9 @@ bool simple_wallet::start_mining(const std::vector& args) } else if (1 == args.size()) { - uint16_t num; + uint16_t num = 1; ok = string_tools::get_xtype_from_string(num, args[0]); - ok &= (1 <= num && num <= max_mining_threads_count); + ok = ok && (1 <= num && num <= max_mining_threads_count); req.threads_count = num; } else @@ -663,7 +650,7 @@ bool simple_wallet::show_payments(const std::vector &args) for(std::string arg : args) { crypto::hash payment_id; - if(parse_payment_id(arg, payment_id)) + if (tools::wallet2::parse_payment_id(arg, payment_id)) { std::list payments; m_wallet->get_payments(payment_id, payments); @@ -746,7 +733,7 @@ bool simple_wallet::transfer(const std::vector &args_) local_args.pop_back(); crypto::hash payment_id; - bool r = parse_payment_id(payment_id_str, payment_id); + bool r = tools::wallet2::parse_payment_id(payment_id_str, payment_id); if(r) { std::string extra_nonce; @@ -785,7 +772,7 @@ bool simple_wallet::transfer(const std::vector &args_) try { cryptonote::transaction tx; - m_wallet->transfer(dsts, fake_outs_count, 0, DEFAULT_FEE, extra, tx); + m_wallet->transfer(dsts, fake_outs_count, 0, MINIMUM_FEE, extra, tx); success_msg_writer(true) << "Money successfully sent, transaction " << get_transaction_hash(tx); } catch (const tools::error::daemon_busy&) diff --git a/src/version.h.in b/src/version.h.in index 05270416..62316da3 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.10" -#define PROJECT_VERSION_BUILD_NO "71" +#define PROJECT_VERSION "0.8.11" +#define PROJECT_VERSION_BUILD_NO "65" #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 new file mode 100644 index 00000000..a327e1a2 --- /dev/null +++ b/src/wallet/Wallet.cpp @@ -0,0 +1,491 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "Wallet.h" +#include "wallet_errors.h" +#include "string_tools.h" +#include "serialization/binary_utils.h" +#include "storages/portable_storage_template_helper.h" +#include "WalletUtils.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "WalletSerialization.h" +#include +#include + +namespace { + +void throwNotDefined() { + throw std::runtime_error("The behavior is not defined!"); +} + +bool verifyKeys(const crypto::secret_key& sec, const crypto::public_key& expected_pub) { + crypto::public_key pub; + bool r = crypto::secret_key_to_public_key(sec, pub); + return r && expected_pub == pub; +} + +void throwIfKeysMissmatch(const crypto::secret_key& sec, const crypto::public_key& expected_pub) { + if (!verifyKeys(sec, expected_pub)) + throw std::system_error(make_error_code(cryptonote::error::WRONG_PASSWORD)); +} + +class ContextCounterHolder +{ +public: + ContextCounterHolder(CryptoNote::WalletAsyncContextCounter& shutdowner) : m_shutdowner(shutdowner) {} + ~ContextCounterHolder() { m_shutdowner.delAsyncContext(); } + +private: + CryptoNote::WalletAsyncContextCounter& m_shutdowner; +}; + +template +void runAtomic(std::mutex& mutex, F f) { + std::unique_lock lock(mutex); + f(); +} +} //namespace + +namespace CryptoNote { + +Wallet::Wallet(INode& node) : + m_state(NOT_INITIALIZED), + m_node(node), + m_isSynchronizing(false), + m_isStopping(false), + m_transferDetails(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_autoRefresher.reset(new WalletNodeObserver(this)); +} + +void Wallet::addObserver(IWalletObserver* observer) { + m_observerManager.add(observer); +} + +void Wallet::removeObserver(IWalletObserver* observer) { + m_observerManager.remove(observer); +} + +void Wallet::initAndGenerate(const std::string& password) { + { + std::unique_lock stateLock(m_cacheMutex); + + if (m_state != NOT_INITIALIZED) { + throw std::system_error(make_error_code(cryptonote::error::NOT_INITIALIZED)); + } + + m_node.addObserver(m_autoRefresher.get()); + + m_account.generate(); + m_password = password; + + m_sender.init(m_account.get_keys()); + + storeGenesisBlock(); + + m_state = INITIALIZED; + } + + m_observerManager.notify(&IWalletObserver::initCompleted, std::error_code()); + refresh(); +} + +void Wallet::storeGenesisBlock() { + cryptonote::block b; + cryptonote::generate_genesis_block(b); + m_blockchain.push_back(get_block_hash(b)); +} + +void Wallet::initAndLoad(std::istream& source, const std::string& password) { + std::unique_lock stateLock(m_cacheMutex); + + if (m_state != NOT_INITIALIZED) { + throw std::system_error(make_error_code(cryptonote::error::NOT_INITIALIZED)); + } + + m_node.addObserver(m_autoRefresher.get()); + + m_password = password; + m_state = LOADING; + + std::thread loader(&Wallet::doLoad, this, std::ref(source)); + loader.detach(); +} + +void Wallet::doLoad(std::istream& source) { + try + { + std::unique_lock lock(m_cacheMutex); + + boost::archive::binary_iarchive ar(source); + + crypto::chacha8_iv iv; + std::string chacha_str;; + ar >> chacha_str; + + ::serialization::parse_binary(chacha_str, iv); + + std::string cipher; + ar >> cipher; + + std::string plain; + decrypt(cipher, plain, iv, m_password); + + std::stringstream restore(plain); + + try + { + //boost archive ctor throws an exception if password is wrong (i.e. there's garbage in a stream) + boost::archive::binary_iarchive dataArchive(restore); + + 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); + + dataArchive >> m_blockchain; + + m_transferDetails.load(dataArchive); + m_unconfirmedTransactions.load(dataArchive); + m_transactionsCache.load(dataArchive); + } + catch (std::exception&) { + throw std::system_error(make_error_code(cryptonote::error::WRONG_PASSWORD)); + } + + m_sender.init(m_account.get_keys()); + } + catch (std::system_error& e) { + runAtomic(m_cacheMutex, [this] () {this->m_state = Wallet::NOT_INITIALIZED;} ); + m_observerManager.notify(&IWalletObserver::initCompleted, e.code()); + return; + } + catch (std::exception&) { + runAtomic(m_cacheMutex, [this] () {this->m_state = Wallet::NOT_INITIALIZED;} ); + m_observerManager.notify(&IWalletObserver::initCompleted, make_error_code(cryptonote::error::INTERNAL_WALLET_ERROR)); + return; + } + + runAtomic(m_cacheMutex, [this] () {this->m_state = Wallet::INITIALIZED;} ); + + m_observerManager.notify(&IWalletObserver::initCompleted, std::error_code()); + + refresh(); +} + +void Wallet::decrypt(const std::string& cipher, std::string& plain, crypto::chacha8_iv iv, const std::string& password) { + crypto::chacha8_key key; + crypto::cn_context context; + crypto::generate_chacha8_key(context, password, key); + + plain.resize(cipher.size()); + + crypto::chacha8(cipher.data(), cipher.size(), key, iv, &plain[0]); +} + +void Wallet::shutdown() { + { + std::unique_lock lock(m_cacheMutex); + + if (m_isStopping) + throwNotDefined(); + + m_isStopping = true; + + if (m_state == NOT_INITIALIZED) + throwNotDefined(); + + m_sender.stop(); + m_synchronizer.stop(); + } + + m_asyncContextCounter.waitAsyncContextsFinish(); + m_node.removeObserver(m_autoRefresher.get()); +} + +void Wallet::save(std::ostream& destination, bool saveDetailed, bool saveCache) { + if(m_isStopping) { + m_observerManager.notify(&IWalletObserver::saveCompleted, make_error_code(cryptonote::error::OPERATION_CANCELLED)); + return; + } + + { + std::unique_lock lock(m_cacheMutex); + + throwIf(m_state != INITIALIZED, cryptonote::error::WRONG_STATE); + + m_state = SAVING; + } + + m_asyncContextCounter.addAsyncContext(); + std::thread saver(&Wallet::doSave, this, std::ref(destination), saveDetailed, saveCache); + saver.detach(); +} + +void Wallet::doSave(std::ostream& destination, bool saveDetailed, bool saveCache) { + ContextCounterHolder counterHolder(m_asyncContextCounter); + + try { + //TODO: exception safety: leave destination stream empty in case of errors + boost::archive::binary_oarchive ar(destination); + + std::stringstream original; + std::unique_lock lock(m_cacheMutex); + + boost::archive::binary_oarchive archive(original); + + archive << m_account; + + const BlockchainContainer& blockchain = saveCache ? m_blockchain : BlockchainContainer(); + + archive << blockchain; + + m_transferDetails.save(archive, saveCache); + m_unconfirmedTransactions.save(archive, saveCache); + m_transactionsCache.save(archive, saveDetailed, saveCache); + + std::string plain = original.str(); + std::string cipher; + + crypto::chacha8_iv iv = encrypt(plain, cipher); + + std::string chacha_str; + ::serialization::dump_binary(iv, chacha_str); + ar << chacha_str; + ar << cipher; + + m_state = INITIALIZED; + } + catch (std::system_error& e) { + runAtomic(m_cacheMutex, [this] () {this->m_state = Wallet::INITIALIZED;} ); + m_observerManager.notify(&IWalletObserver::saveCompleted, e.code()); + return; + } + catch (std::exception&) { + runAtomic(m_cacheMutex, [this] () {this->m_state = Wallet::INITIALIZED;} ); + m_observerManager.notify(&IWalletObserver::saveCompleted, make_error_code(cryptonote::error::INTERNAL_WALLET_ERROR)); + return; + } + + m_observerManager.notify(&IWalletObserver::saveCompleted, std::error_code()); +} + +crypto::chacha8_iv Wallet::encrypt(const std::string& plain, std::string& cipher) { + crypto::chacha8_key key; + crypto::cn_context context; + crypto::generate_chacha8_key(context, m_password, key); + + cipher.resize(plain.size()); + + crypto::chacha8_iv iv = crypto::rand(); + crypto::chacha8(plain.data(), plain.size(), key, iv, &cipher[0]); + + return iv; +} + +std::error_code Wallet::changePassword(const std::string& oldPassword, const std::string& newPassword) { + std::unique_lock passLock(m_cacheMutex); + + throwIfNotInitialised(); + + if (m_password.compare(oldPassword)) + return make_error_code(cryptonote::error::WRONG_PASSWORD); + + //we don't let the user to change the password while saving + m_password = newPassword; + + return std::error_code(); +} + +std::string Wallet::getAddress() { + std::unique_lock lock(m_cacheMutex); + throwIfNotInitialised(); + + return m_account.get_public_address_str(); +} + +uint64_t Wallet::actualBalance() { + std::unique_lock lock(m_cacheMutex); + throwIfNotInitialised(); + + return m_transferDetails.countActualBalance(); +} + +uint64_t Wallet::pendingBalance() { + std::unique_lock lock(m_cacheMutex); + throwIfNotInitialised(); + + return doPendingBalance(); +} + +uint64_t Wallet::doPendingBalance() { + uint64_t amount = 0; + amount = m_transferDetails.countPendingBalance(); + amount += m_unconfirmedTransactions.countPendingBalance(); + + return amount; +} + +size_t Wallet::getTransactionCount() { + std::unique_lock lock(m_cacheMutex); + throwIfNotInitialised(); + + return m_transactionsCache.getTransactionCount(); +} + +size_t Wallet::getTransferCount() { + std::unique_lock lock(m_cacheMutex); + throwIfNotInitialised(); + + return m_transactionsCache.getTransferCount(); +} + +TransactionId Wallet::findTransactionByTransferId(TransferId transferId) { + std::unique_lock lock(m_cacheMutex); + throwIfNotInitialised(); + + return m_transactionsCache.findTransactionByTransferId(transferId); +} + +bool Wallet::getTransaction(TransactionId transactionId, Transaction& transaction) { + std::unique_lock lock(m_cacheMutex); + throwIfNotInitialised(); + + return m_transactionsCache.getTransaction(transactionId, transaction); +} + +bool Wallet::getTransfer(TransferId transferId, Transfer& transfer) { + std::unique_lock lock(m_cacheMutex); + throwIfNotInitialised(); + + return m_transactionsCache.getTransfer(transferId, transfer); +} + +TransactionId Wallet::sendTransaction(const Transfer& transfer, uint64_t fee, const std::string& extra, uint64_t mixIn, uint64_t unlockTimestamp) { + std::vector transfers; + transfers.push_back(transfer); + + return sendTransaction(transfers, fee, extra, mixIn, unlockTimestamp); +} + +TransactionId Wallet::sendTransaction(const std::vector& transfers, uint64_t fee, const std::string& extra, uint64_t mixIn, uint64_t unlockTimestamp) { + TransactionId txId = 0; + std::shared_ptr request; + std::deque > events; + + { + std::unique_lock lock(m_cacheMutex); + request = m_sender.makeSendRequest(txId, events, transfers, fee, extra, mixIn, unlockTimestamp); + } + + notifyClients(events); + + if (request) { + m_asyncContextCounter.addAsyncContext(); + request->perform(m_node, std::bind(&Wallet::sendTransactionCallback, this, std::placeholders::_1, std::placeholders::_2)); + } + + return txId; +} + +void Wallet::sendTransactionCallback(WalletRequest::Callback callback, std::error_code ec) { + ContextCounterHolder counterHolder(m_asyncContextCounter); + std::deque > events; + + boost::optional > nextRequest; + { + std::unique_lock lock(m_cacheMutex); + callback(events, nextRequest, ec); + } + + notifyClients(events); + + if (nextRequest) { + m_asyncContextCounter.addAsyncContext(); + (*nextRequest)->perform(m_node, std::bind(&Wallet::synchronizationCallback, this, std::placeholders::_1, std::placeholders::_2)); + } + +} + +void Wallet::synchronizationCallback(WalletRequest::Callback callback, std::error_code ec) { + ContextCounterHolder counterHolder(m_asyncContextCounter); + + std::deque > events; + boost::optional > nextRequest; + { + std::unique_lock lock(m_cacheMutex); + callback(events, nextRequest, ec); + + if (!nextRequest) + m_isSynchronizing = false; + } + + notifyClients(events); + + if (nextRequest) { + m_asyncContextCounter.addAsyncContext(); + (*nextRequest)->perform(m_node, std::bind(&Wallet::synchronizationCallback, this, std::placeholders::_1, std::placeholders::_2)); + } +} + +std::error_code Wallet::cancelTransaction(size_t transactionId) { + return make_error_code(cryptonote::error::TX_CANCEL_IMPOSSIBLE); +} + +void Wallet::throwIfNotInitialised() { + if (m_state == NOT_INITIALIZED || m_state == LOADING) + throw std::system_error(make_error_code(cryptonote::error::NOT_INITIALIZED)); +} + +void Wallet::startRefresh() { + refresh(); +} + +void Wallet::refresh() { + if (m_isStopping) + return; + + std::shared_ptr req; + { + std::unique_lock lock(m_cacheMutex); + + if (m_state != INITIALIZED) { + return; + } + + if (m_isSynchronizing) { + return; + } + + m_isSynchronizing = true; + + req = m_synchronizer.makeStartRefreshRequest(); + } + + m_asyncContextCounter.addAsyncContext(); + req->perform(m_node, std::bind(&Wallet::synchronizationCallback, this, std::placeholders::_1, std::placeholders::_2)); +} + +void Wallet::notifyClients(std::deque >& events) { + while (!events.empty()) { + std::shared_ptr event = events.front(); + event->notify(m_observerManager); + events.pop_front(); + } +} + +} //namespace CryptoNote diff --git a/src/wallet/Wallet.h b/src/wallet/Wallet.h new file mode 100644 index 00000000..6ca795d3 --- /dev/null +++ b/src/wallet/Wallet.h @@ -0,0 +1,124 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include +#include +#include +#include +#include + +#include "IWallet.h" +#include "INode.h" +#include "WalletErrors.h" +#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 "WalletTransferDetails.h" +#include "WalletUserTransactionsCache.h" +#include "WalletUnconfirmedTransactions.h" +#include "WalletSynchronizer.h" +#include "WalletTransactionSender.h" +#include "WalletRequest.h" + +namespace CryptoNote { + +class Wallet : public IWallet { +public: + Wallet(INode& node); + ~Wallet() {}; + + virtual void addObserver(IWalletObserver* observer); + virtual void removeObserver(IWalletObserver* observer); + + virtual void initAndGenerate(const std::string& password); + virtual void initAndLoad(std::istream& source, const std::string& password); + virtual void shutdown(); + + virtual void save(std::ostream& destination, bool saveDetailed = true, bool saveCache = true); + + virtual std::error_code changePassword(const std::string& oldPassword, const std::string& newPassword); + + virtual std::string getAddress(); + + virtual uint64_t actualBalance(); + virtual uint64_t pendingBalance(); + + virtual size_t getTransactionCount(); + virtual size_t getTransferCount(); + + virtual TransactionId findTransactionByTransferId(TransferId transferId); + + virtual bool getTransaction(TransactionId transactionId, Transaction& 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); + virtual TransactionId sendTransaction(const std::vector& transfers, uint64_t fee, const std::string& extra = "", uint64_t mixIn = 0, uint64_t unlockTimestamp = 0); + virtual std::error_code cancelTransaction(size_t transactionId); + + void startRefresh(); + +private: + void throwIfNotInitialised(); + void refresh(); + uint64_t doPendingBalance(); + + void doSave(std::ostream& destination, bool saveDetailed, bool saveCache); + void doLoad(std::istream& source); + + crypto::chacha8_iv encrypt(const std::string& plain, std::string& cipher); + void decrypt(const std::string& cipher, std::string& plain, crypto::chacha8_iv iv, const std::string& password); + + void synchronizationCallback(WalletRequest::Callback callback, std::error_code ec); + void sendTransactionCallback(WalletRequest::Callback callback, std::error_code ec); + void notifyClients(std::deque >& events); + + void storeGenesisBlock(); + + enum WalletState + { + NOT_INITIALIZED = 0, + INITIALIZED, + LOADING, + SAVING + }; + + WalletState m_state; + std::mutex m_cacheMutex; + cryptonote::account_base m_account; + std::string m_password; + INode& m_node; + bool m_isSynchronizing; + bool m_isStopping; + + typedef std::vector BlockchainContainer; + + BlockchainContainer m_blockchain; + WalletTransferDetails m_transferDetails; + WalletUnconfirmedTransactions m_unconfirmedTransactions; + tools::ObserverManager m_observerManager; + + WalletTxSendingState m_sendingTxsStates; + WalletUserTransactionsCache m_transactionsCache; + + struct WalletNodeObserver: public INodeObserver + { + WalletNodeObserver(Wallet* wallet) : m_wallet(wallet) {} + virtual void lastKnownBlockHeightUpdated(uint64_t height) { m_wallet->startRefresh(); } + + Wallet* m_wallet; + }; + + std::unique_ptr m_autoRefresher; + WalletAsyncContextCounter m_asyncContextCounter; + + WalletSynchronizer m_synchronizer; + WalletTransactionSender m_sender; +}; + +} //namespace CryptoNote diff --git a/src/wallet/WalletAsyncContextCounter.cpp b/src/wallet/WalletAsyncContextCounter.cpp new file mode 100644 index 00000000..752933b6 --- /dev/null +++ b/src/wallet/WalletAsyncContextCounter.cpp @@ -0,0 +1,29 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "WalletAsyncContextCounter.h" + +namespace CryptoNote { + +void WalletAsyncContextCounter::addAsyncContext() { + std::unique_lock lock(m_mutex); + m_asyncContexts++; +} + +void WalletAsyncContextCounter::delAsyncContext() { + std::unique_lock lock(m_mutex); + m_asyncContexts--; + + if (!m_asyncContexts) m_cv.notify_one(); +} + +void WalletAsyncContextCounter::waitAsyncContextsFinish() { + std::unique_lock lock(m_mutex); + while (m_asyncContexts) + m_cv.wait(lock); +} + +} //namespace CryptoNote + + diff --git a/src/wallet/WalletAsyncContextCounter.h b/src/wallet/WalletAsyncContextCounter.h new file mode 100644 index 00000000..c7acd662 --- /dev/null +++ b/src/wallet/WalletAsyncContextCounter.h @@ -0,0 +1,30 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include +#include +#include + +namespace CryptoNote { + +class WalletAsyncContextCounter +{ +public: + WalletAsyncContextCounter() : m_asyncContexts(0) {} + + void addAsyncContext(); + void delAsyncContext(); + + //returns true if contexts are finished before timeout + void waitAsyncContextsFinish(); + +private: + uint32_t m_asyncContexts; + std::condition_variable m_cv; + std::mutex m_mutex; +}; + +} //namespace CryptoNote diff --git a/src/wallet/WalletErrors.cpp b/src/wallet/WalletErrors.cpp new file mode 100644 index 00000000..792c046d --- /dev/null +++ b/src/wallet/WalletErrors.cpp @@ -0,0 +1,13 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "WalletErrors.h" + +namespace cryptonote { +namespace error { + +WalletErrorCategory WalletErrorCategory::INSTANCE; + +} +} diff --git a/src/wallet/WalletErrors.h b/src/wallet/WalletErrors.h new file mode 100644 index 00000000..e62617f2 --- /dev/null +++ b/src/wallet/WalletErrors.h @@ -0,0 +1,73 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include +#include + +namespace cryptonote { +namespace error { + +// custom error conditions enum type: +enum WalletErrorCodes { + NOT_INITIALIZED = 1, + ALREADY_INITIALIZED, + WRONG_STATE, + WRONG_PASSWORD, + INTERNAL_WALLET_ERROR, + MIXIN_COUNT_TOO_BIG, + BAD_ADDRESS, + TRANSACTION_SIZE_TOO_BIG, + WRONG_AMOUNT, + SUM_OVERFLOW, + ZERO_DESTINATION, + TX_CANCEL_IMPOSSIBLE, + TX_CANCELLED, + OPERATION_CANCELLED +}; + +// custom category: +class WalletErrorCategory : public std::error_category { +public: + static WalletErrorCategory INSTANCE; + + virtual const char* name() const throw() { + return "WalletErrorCategory"; + } + + virtual std::error_condition default_error_condition(int ev) const throw() { + return std::error_condition(ev, *this); + } + + virtual std::string message(int ev) const { + switch (ev) { + case NOT_INITIALIZED: return "Object was not initialized"; + case WRONG_PASSWORD: return "The password is wrong"; + case ALREADY_INITIALIZED: return "The object is already initialized"; + case INTERNAL_WALLET_ERROR: return "Internal error occured"; + case MIXIN_COUNT_TOO_BIG: return "MixIn count is too big"; + case BAD_ADDRESS: return "Bad address"; + case TRANSACTION_SIZE_TOO_BIG: return "Transaction size is too big"; + case WRONG_AMOUNT: return "Wrong amount"; + case SUM_OVERFLOW: return "Sum overflow"; + case ZERO_DESTINATION: return "The destination is empty"; + case TX_CANCEL_IMPOSSIBLE: return "Impossible to cancel transaction"; + case WRONG_STATE: return "The wallet is in wrong state (maybe loading or saving), try again later"; + case OPERATION_CANCELLED: return "The operation you've requested has been cancelled"; + default: return "Unknown error"; + } + } + +private: + WalletErrorCategory() { + } +}; + +} +} + +inline std::error_code make_error_code(cryptonote::error::WalletErrorCodes e) { + return std::error_code(static_cast(e), cryptonote::error::WalletErrorCategory::INSTANCE); +} diff --git a/src/wallet/WalletEvent.h b/src/wallet/WalletEvent.h new file mode 100644 index 00000000..10bc210a --- /dev/null +++ b/src/wallet/WalletEvent.h @@ -0,0 +1,113 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include "IWallet.h" +#include "common/ObserverManager.h" + +namespace CryptoNote +{ + +class WalletEvent +{ +public: + virtual ~WalletEvent() {}; + + virtual void notify(tools::ObserverManager& observer) = 0; +}; + +class WalletTransactionUpdatedEvent : public WalletEvent +{ +public: + WalletTransactionUpdatedEvent(TransactionId transactionId) : m_id(transactionId) {}; + virtual ~WalletTransactionUpdatedEvent() {}; + + virtual void notify(tools::ObserverManager& observer) + { + observer.notify(&IWalletObserver::transactionUpdated, m_id); + } + +private: + TransactionId m_id; +}; + +class WalletSendTransactionCompletedEvent : public WalletEvent +{ +public: + WalletSendTransactionCompletedEvent(TransactionId transactionId, std::error_code result) : m_id(transactionId), m_error(result) {}; + virtual ~WalletSendTransactionCompletedEvent() {}; + + virtual void notify(tools::ObserverManager& observer) + { + observer.notify(&IWalletObserver::sendTransactionCompleted, m_id, m_error); + } + +private: + TransactionId m_id; + std::error_code m_error; +}; + +class WalletExternalTransactionCreatedEvent : public WalletEvent +{ +public: + WalletExternalTransactionCreatedEvent(TransactionId transactionId) : m_id(transactionId) {}; + virtual ~WalletExternalTransactionCreatedEvent() {}; + + virtual void notify(tools::ObserverManager& observer) + { + observer.notify(&IWalletObserver::externalTransactionCreated, m_id); + } +private: + TransactionId m_id; +}; + +class WalletSynchronizationProgressUpdatedEvent : public WalletEvent +{ +public: + WalletSynchronizationProgressUpdatedEvent(uint64_t current, uint64_t total, std::error_code result) : m_current(current), m_total(total), m_ec(result) {}; + virtual ~WalletSynchronizationProgressUpdatedEvent() {}; + + virtual void notify(tools::ObserverManager& observer) + { + observer.notify(&IWalletObserver::synchronizationProgressUpdated, m_current, m_total, m_ec); + } + +private: + uint64_t m_current; + uint64_t m_total; + std::error_code m_ec; +}; + + +class WalletActualBalanceUpdatedEvent : public WalletEvent +{ +public: + WalletActualBalanceUpdatedEvent(uint64_t balance) : m_balance(balance) {}; + virtual ~WalletActualBalanceUpdatedEvent() {}; + + virtual void notify(tools::ObserverManager& observer) + { + observer.notify(&IWalletObserver::actualBalanceUpdated, m_balance); + } +private: + uint64_t m_balance; +}; + +class WalletPendingBalanceUpdatedEvent : public WalletEvent +{ +public: + WalletPendingBalanceUpdatedEvent(uint64_t balance) : m_balance(balance) {}; + virtual ~WalletPendingBalanceUpdatedEvent() {}; + + virtual void notify(tools::ObserverManager& observer) + { + observer.notify(&IWalletObserver::pendingBalanceUpdated, m_balance); + } +private: + uint64_t m_balance; +}; + +} /* namespace CryptoNote */ + diff --git a/src/wallet/WalletRequest.h b/src/wallet/WalletRequest.h new file mode 100644 index 00000000..d3b43df7 --- /dev/null +++ b/src/wallet/WalletRequest.h @@ -0,0 +1,95 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include "INode.h" + +#include "WalletSynchronizationContext.h" +#include "WalletSendTransactionContext.h" +#include "WalletEvent.h" + +namespace CryptoNote { + +class WalletRequest +{ +public: + typedef std::function >& events, boost::optional >& nextRequest, std::error_code ec)> Callback; + + virtual ~WalletRequest() {}; + + virtual void perform(INode& node, std::function cb) = 0; +}; + +class WalletGetNewBlocksRequest: public WalletRequest +{ +public: + WalletGetNewBlocksRequest(const std::list& knownBlockIds, std::shared_ptr context, Callback cb) : m_ids(knownBlockIds), m_context(context), m_cb(cb) {}; + virtual ~WalletGetNewBlocksRequest() {}; + + virtual void perform(INode& node, std::function cb) + { + node.getNewBlocks(std::move(m_ids), std::ref(m_context->newBlocks), std::ref(m_context->startHeight), std::bind(cb, m_cb, std::placeholders::_1)); + }; + +private: + std::shared_ptr m_context; + std::list m_ids; + Callback m_cb; +}; + +class WalletGetTransactionOutsGlobalIndicesRequest: public WalletRequest +{ +public: + WalletGetTransactionOutsGlobalIndicesRequest(const crypto::hash& transactionHash, std::vector& outsGlobalIndices, Callback cb) : m_hash(transactionHash), m_outs(outsGlobalIndices), m_cb(cb) {}; + virtual ~WalletGetTransactionOutsGlobalIndicesRequest() {}; + + virtual void perform(INode& node, std::function cb) + { + node.getTransactionOutsGlobalIndices(m_hash, std::ref(m_outs), std::bind(cb, m_cb, std::placeholders::_1)); + }; + +private: + crypto::hash m_hash; + std::vector& m_outs; + Callback m_cb; +}; + +class WalletGetRandomOutsByAmountsRequest: public WalletRequest +{ +public: + WalletGetRandomOutsByAmountsRequest(const std::vector& amounts, uint64_t outsCount, std::shared_ptr context, Callback cb) : + m_amounts(amounts), m_outsCount(outsCount), m_context(context), m_cb(cb) {}; + + virtual ~WalletGetRandomOutsByAmountsRequest() {}; + + virtual void perform(INode& node, std::function cb) + { + node.getRandomOutsByAmounts(std::move(m_amounts), m_outsCount, std::ref(m_context->outs), std::bind(cb, m_cb, std::placeholders::_1)); + }; + +private: + std::vector m_amounts; + uint64_t m_outsCount; + std::shared_ptr m_context; + Callback m_cb; +}; + +class WalletRelayTransactionRequest: public WalletRequest +{ +public: + WalletRelayTransactionRequest(const cryptonote::transaction& tx, Callback cb) : m_tx(tx), m_cb(cb) {}; + virtual ~WalletRelayTransactionRequest() {}; + + virtual void perform(INode& node, std::function cb) + { + node.relayTransaction(m_tx, std::bind(cb, m_cb, std::placeholders::_1)); + } + +private: + cryptonote::transaction m_tx; + Callback m_cb; +}; + +} //namespace CryptoNote diff --git a/src/wallet/WalletSendTransactionContext.h b/src/wallet/WalletSendTransactionContext.h new file mode 100644 index 00000000..796d5573 --- /dev/null +++ b/src/wallet/WalletSendTransactionContext.h @@ -0,0 +1,40 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include +#include + +#include "cryptonote_core/cryptonote_basic.h" +#include "IWallet.h" + +namespace CryptoNote { + +struct TxDustPolicy +{ + uint64_t dustThreshold; + bool addToFee; + cryptonote::account_public_address 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()) + : dustThreshold(a_dust_threshold) + , addToFee(an_add_to_fee) + , addrForDust(an_addr_for_dust) + { + } +}; + +struct SendTransactionContext +{ + TransactionId transactionId; + std::vector outs; + uint64_t foundMoney; + std::list selectedTransfers; + uint64_t unlockTimestamp; + TxDustPolicy dustPolicy; + uint64_t mixIn; +}; + +} //namespace CryptoNote diff --git a/src/wallet/WalletSerialization.h b/src/wallet/WalletSerialization.h new file mode 100644 index 00000000..5d8e2a1a --- /dev/null +++ b/src/wallet/WalletSerialization.h @@ -0,0 +1,80 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include +#include +#include +#include +#include + +#include "cryptonote_core/cryptonote_boost_serialization.h" +#include "common/unordered_containers_boost_serialization.h" +#include "storages/portable_storage_template_helper.h" + +BOOST_SERIALIZATION_SPLIT_FREE(cryptonote::account_base); + +namespace boost { +namespace serialization { + +template +inline void load(Archive & ar, cryptonote::account_base& account, const unsigned int version) +{ + std::string data; + ar >> data; + epee::serialization::load_t_from_binary(account, 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); + ar << data; +} + +template +inline void serialize(Archive & ar, CryptoNote::Transaction& tx, const unsigned int version) +{ + ar & tx.firstTransferId; + ar & tx.transferCount; + ar & tx.totalAmount; + ar & tx.fee; + ar & make_array(tx.hash.data(), tx.hash.size()); + ar & tx.isCoinbase; + ar & tx.blockHeight; + ar & tx.timestamp; + ar & tx.extra; +} + +template +inline void serialize(Archive & ar, CryptoNote::Transfer& tr, const unsigned int version) +{ + ar & tr.address; + ar & tr.amount; +} + +template +inline void serialize(Archive & ar, CryptoNote::TransferDetails& details, const unsigned int version) +{ + ar & details.blockHeight; + ar & details.tx; + ar & details.internalOutputIndex; + ar & details.globalOutputIndex; + ar & details.spent; + ar & details.keyImage; +} + +template +inline void serialize(Archive & ar, CryptoNote::UnconfirmedTransferDetails& details, const unsigned int version) +{ + ar & details.tx; + ar & details.change; + ar & details.sentTime; + ar & details.transactionId; +} + +} // namespace serialization +} // namespace boost diff --git a/src/wallet/WalletSynchronizationContext.h b/src/wallet/WalletSynchronizationContext.h new file mode 100644 index 00000000..423da494 --- /dev/null +++ b/src/wallet/WalletSynchronizationContext.h @@ -0,0 +1,42 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include +#include +#include +#include +#include + +#include "crypto/crypto.h" +#include "cryptonote_core/cryptonote_basic.h" + +namespace CryptoNote { + +struct TransactionContextInfo +{ + std::vector requestedOuts; + std::vector globalIndices; + cryptonote::transaction transaction; + crypto::public_key transactionPubKey; +}; + +struct SynchronizationState +{ + SynchronizationState() : blockIdx(0), transactionIdx(0), minersTxProcessed(false) {} + size_t blockIdx; //block index within context->new_blocks array to be processed + size_t transactionIdx; //tx index within the block to be processed + bool minersTxProcessed; //is miner's tx in the block processed +}; + +struct SynchronizationContext +{ + std::list newBlocks; + uint64_t startHeight; + std::unordered_map transactionContext; + SynchronizationState progress; +}; + +} //namespace CryptoNote diff --git a/src/wallet/WalletSynchronizer.cpp b/src/wallet/WalletSynchronizer.cpp new file mode 100644 index 00000000..9b948156 --- /dev/null +++ b/src/wallet/WalletSynchronizer.cpp @@ -0,0 +1,475 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "WalletSynchronizer.h" + +#include + +#include "WalletErrors.h" +#include "WalletUtils.h" + +namespace { + +void throwIf(bool expr, cryptonote::error::WalletErrorCodes ec) { + if (expr) + throw std::system_error(make_error_code(ec)); +} + +bool getTxPubKey(const cryptonote::transaction& tx, crypto::public_key& key) { + std::vector extraFields; + cryptonote::parse_tx_extra(tx.extra, extraFields); + + cryptonote::tx_extra_pub_key pubKeyField; + if(!cryptonote::find_tx_extra_field_by_type(extraFields, pubKeyField)) { + //Public key wasn't found in the transaction extra. Skipping transaction + return false; + } + + key = pubKeyField.pub_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) { + 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 amount = 0; + for (const cryptonote::tx_out& o: tx.vout) { + amount += o.amount; + } + + return amount; +} + +uint64_t countOverallTxInputs(const cryptonote::transaction& tx) { + uint64_t amount = 0; + for (auto& in: tx.vin) { + if(in.type() != typeid(cryptonote::txin_to_key)) + continue; + + amount += boost::get(in).amount; + } + + return amount; +} + +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()); +} + +} + +namespace CryptoNote +{ + +WalletSynchronizer::WalletSynchronizer(const cryptonote::account_base& account, INode& node, std::vector& blockchain, + WalletTransferDetails& transferDetails, WalletUnconfirmedTransactions& unconfirmedTransactions, + WalletUserTransactionsCache& transactionsCache) : + m_account(account), + m_node(node), + m_blockchain(blockchain), + m_transferDetails(transferDetails), + m_unconfirmedTransactions(unconfirmedTransactions), + m_transactionsCache(transactionsCache), + m_actualBalance(0), + m_pendingBalance(0), + m_isStoping(false) { +} + +void WalletSynchronizer::stop() { + m_isStoping = true; +} + +std::shared_ptr WalletSynchronizer::makeStartRefreshRequest() { + std::shared_ptr context = std::make_shared(); + std::shared_ptr request = makeGetNewBlocksRequest(context); + + return request; +} + +void WalletSynchronizer::postGetTransactionOutsGlobalIndicesRequest(ProcessParameters& parameters, const crypto::hash& hash, std::vector& outsGlobalIndices, uint64_t height) { + parameters.nextRequest = std::make_shared(hash, outsGlobalIndices, + std::bind(&WalletSynchronizer::handleTransactionOutGlobalIndicesResponse, this, parameters.context, hash, height, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); +} + +std::shared_ptr WalletSynchronizer::makeGetNewBlocksRequest(std::shared_ptr context) { + context->newBlocks.clear(); + context->startHeight = 0; + context->progress = SynchronizationState(); + + std::list ids; + getShortChainHistory(ids); + + std::shared_ptrreq = std::make_shared(ids, context, std::bind(&WalletSynchronizer::handleNewBlocksPortion, this, + context, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + + return req; +} + +void WalletSynchronizer::getShortChainHistory(std::list& ids) { + size_t i = 0; + size_t current_multiplier = 1; + size_t sz = m_blockchain.size(); + + if(!sz) + return; + + size_t current_back_offset = 1; + bool genesis_included = false; + + while(current_back_offset < sz) { + ids.push_back(m_blockchain[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_blockchain[0]); +} + +void WalletSynchronizer::handleNewBlocksPortion(std::shared_ptr context, std::deque >& events, + boost::optional >& nextRequest, std::error_code ec) { + if (m_isStoping) { + return; + } + + if (ec) { + events.push_back(std::make_shared(context->startHeight, m_node.getLastLocalBlockHeight(), ec)); + return; + } + + ProcessParameters parameters; + parameters.context = context; + + try + { + bool fillRequest = processNewBlocks(parameters); + + if (fillRequest) { + parameters.nextRequest = makeGetNewBlocksRequest(context); + } + } + catch (std::system_error& e) { + parameters.nextRequest = boost::none; + parameters.events.push_back(std::make_shared(context->startHeight, m_node.getLastLocalBlockHeight(), e.code())); + } + catch (std::exception&) { + parameters.nextRequest = boost::none; + parameters.events.push_back(std::make_shared(context->startHeight, m_node.getLastLocalBlockHeight(), make_error_code(cryptonote::error::INTERNAL_WALLET_ERROR))); + } + + refreshBalance(events); + + std::copy(parameters.events.begin(), parameters.events.end(), std::back_inserter(events)); + nextRequest = parameters.nextRequest; +} + +//returns true if new request should be performed +bool WalletSynchronizer::processNewBlocks(ProcessParameters& parameters) { + bool fillRequest = false; + std::shared_ptr context = parameters.context; + + size_t currentIndex = context->startHeight + context->progress.blockIdx; + + try + { + auto blocksIt = context->newBlocks.begin(); + std::advance(blocksIt, context->progress.blockIdx); + + for (; blocksIt != context->newBlocks.end(); ++blocksIt) { + if (m_isStoping) return false; + + auto& blockEntry = *blocksIt; + + NextBlockAction action = handleNewBlockchainEntry(parameters, blockEntry, currentIndex); + + if (action == INTERRUPT) + return false; + else if (action == CONTINUE) + fillRequest = true; + + ++context->progress.blockIdx; + ++currentIndex; + } + } + catch (std::exception& e) { + context->startHeight = currentIndex; + throw e; + } + + return fillRequest; +} + +WalletSynchronizer::NextBlockAction WalletSynchronizer::handleNewBlockchainEntry(ProcessParameters& parameters, cryptonote::block_complete_entry& blockEntry, uint64_t height) { + cryptonote::block b; + bool r = cryptonote::parse_and_validate_block_from_blob(blockEntry.block, b); + throwIf(!r, cryptonote::error::INTERNAL_WALLET_ERROR); + + crypto::hash blockId = get_block_hash(b); + if (height >= m_blockchain.size()) { + r = processNewBlockchainEntry(parameters, blockEntry, b, blockId, height); + + if (r) { + parameters.events.push_back(std::make_shared(height, m_node.getLastLocalBlockHeight(), std::error_code())); + return CONTINUE; + } + return INTERRUPT; + } + + if(blockId != m_blockchain[height]) { + //split detected here !!! + //Wrong daemon response + throwIf(height == parameters.context->startHeight, cryptonote::error::INTERNAL_WALLET_ERROR); + + detachBlockchain(height); + + r = processNewBlockchainEntry(parameters, blockEntry, b, blockId, height); + + if (r) { + parameters.events.push_back(std::make_shared(height, m_node.getLastLocalBlockHeight(), std::error_code())); + return CONTINUE; + } + return INTERRUPT; + } + + //we already have this block. + return SKIP; +} + +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)) + return false; + + auto txIt = blockEntry.txs.begin(); + std::advance(txIt, parameters.context->progress.transactionIdx); + + for (; txIt != blockEntry.txs.end(); ++txIt) { + auto& txblob = *txIt; + cryptonote::transaction tx; + + bool r = parse_and_validate_tx_from_blob(txblob, tx); + throwIf(!r, cryptonote::error::INTERNAL_WALLET_ERROR); + + r = processNewTransaction(parameters, tx, height, false, b.timestamp); + parameters.context->progress.transactionIdx++; + + if (!r) return false; + } + } + + parameters.context->progress.transactionIdx = 0; + + m_blockchain.push_back(blockId); + return true; +} + +bool WalletSynchronizer::processMinersTx(ProcessParameters& parameters, const cryptonote::transaction& tx, uint64_t height, uint64_t timestamp) { + bool r = true; + + if (!parameters.context->progress.minersTxProcessed) { + r = processNewTransaction(parameters, tx, height, true, timestamp); + parameters.context->progress.minersTxProcessed = true; + } + + return r; +} + +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); + std::vector outs; + uint64_t moneyInMyOuts = 0; + + crypto::public_key publicKey; + if (!getTxPubKey(tx, publicKey)) + return true; //Public key wasn't found in the transaction extra. Skipping transaction + + findMyOuts(m_account.get_keys(), tx, publicKey, outs, moneyInMyOuts); + + if(!outs.empty() && moneyInMyOuts) { + fillGetTransactionOutsGlobalIndicesRequest(parameters, tx, outs, publicKey, height); + res = false; + } + + uint64_t moneyInMyInputs = processMyInputs(tx); + + if (!moneyInMyOuts && !moneyInMyInputs) + return res; //There's nothing related to our account, skip it + + updateTransactionsCache(parameters, tx, moneyInMyOuts, moneyInMyInputs, height, isCoinbase, timestamp); + + return res; +} + +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)) + continue; + + size_t idx; + if (!m_transferDetails.getTransferDetailsIdxByKeyImage(boost::get(in).k_image, idx)) + continue; + + money += boost::get(in).amount; + + TransferDetails& td = m_transferDetails.getTransferDetails(idx); + td.spent = true; + } + + return money; +} + +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); + + TransactionContextInfo tx_context; + tx_context.requestedOuts = outs; + tx_context.transaction = tx; + tx_context.transactionPubKey = publicKey; + + auto insert_result = parameters.context->transactionContext.emplace(txid, std::move(tx_context)); + + 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) { + + 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; + transaction.firstTransferId = INVALID_TRANSFER_ID; + transaction.transferCount = 0; + transaction.totalAmount = myOuts - myInputs; + transaction.fee = isCoinbase ? 0 : allInputs - allOuts; + fillTransactionHash(tx, transaction.hash); + transaction.blockHeight = height; + transaction.isCoinbase = isCoinbase; + transaction.timestamp = timestamp; + + TransactionId newId = m_transactionsCache.insertTransaction(std::move(transaction)); + + parameters.events.push_back(std::make_shared(newId)); + } + else + { + Transaction& transaction = m_transactionsCache.getTransaction(foundTx); + transaction.blockHeight = height; + transaction.timestamp = timestamp; + transaction.isCoinbase = isCoinbase; + + parameters.events.push_back(std::make_shared(foundTx)); + } +} + +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); + tr.blockHeight = height; + tr.timestamp = timestamp; + + m_unconfirmedTransactions.erase(hash); + + parameters.events.push_back(std::make_shared(id)); +} + +void WalletSynchronizer::handleTransactionOutGlobalIndicesResponse(std::shared_ptr context, crypto::hash txid, uint64_t height, + std::deque >& events, boost::optional >& nextRequest, std::error_code ec) { + if (m_isStoping) { + return; + } + + if (ec) { + events.push_back(std::make_shared(height, m_node.getLastLocalBlockHeight(), ec)); + return; + } + + try + { + auto it = context->transactionContext.find(txid); + if (it == context->transactionContext.end()) { + events.push_back(std::make_shared(height, m_node.getLastLocalBlockHeight(), make_error_code(cryptonote::error::INTERNAL_WALLET_ERROR))); + return; + } + + 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; + + for (size_t o: outs) { + throwIf(tx.vout.size() <= o, cryptonote::error::INTERNAL_WALLET_ERROR); + + TransferDetails td; + td.blockHeight = height; + td.internalOutputIndex = o; + td.globalOutputIndex = global_indices[o]; + td.tx = tx; + td.spent = false; + 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); + + m_transferDetails.addTransferDetails(td); + } + + context->transactionContext.erase(it); + } + catch (std::exception&) { + events.push_back(std::make_shared(height, m_node.getLastLocalBlockHeight(), make_error_code(cryptonote::error::INTERNAL_WALLET_ERROR))); + return; + } + + handleNewBlocksPortion(context, events, nextRequest, ec); +} + +void WalletSynchronizer::detachBlockchain(uint64_t height) { + m_transferDetails.detachTransferDetails(height); + + m_blockchain.erase(m_blockchain.begin()+height, m_blockchain.end()); + + m_transactionsCache.detachTransactions(height); +} + +void WalletSynchronizer::refreshBalance(std::deque >& events) { + uint64_t actualBalance = m_transferDetails.countActualBalance(); + uint64_t pendingBalance = m_unconfirmedTransactions.countPendingBalance(); + pendingBalance += m_transferDetails.countPendingBalance(); + + if (actualBalance != m_actualBalance) { + events.push_back(std::make_shared(actualBalance)); + m_actualBalance = actualBalance; + } + + if (pendingBalance != m_pendingBalance) { + events.push_back(std::make_shared(pendingBalance)); + m_pendingBalance = pendingBalance; + } +} + +} /* namespace CryptoNote */ diff --git a/src/wallet/WalletSynchronizer.h b/src/wallet/WalletSynchronizer.h new file mode 100644 index 00000000..c86ee131 --- /dev/null +++ b/src/wallet/WalletSynchronizer.h @@ -0,0 +1,83 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include +#include +#include + +#include "INode.h" +#include "crypto/hash.h" +#include "WalletTransferDetails.h" +#include "WalletUnconfirmedTransactions.h" +#include "WalletUserTransactionsCache.h" +#include "WalletEvent.h" +#include "WalletSynchronizationContext.h" +#include "WalletRequest.h" + +namespace CryptoNote { + +class WalletSynchronizer +{ +public: + WalletSynchronizer(const cryptonote::account_base& account, INode& node, std::vector& blockchain, WalletTransferDetails& transferDetails, + WalletUnconfirmedTransactions& unconfirmedTransactions, WalletUserTransactionsCache& transactionsCache); + + std::shared_ptr makeStartRefreshRequest(); + + void stop(); + +private: + + struct ProcessParameters { + std::shared_ptr context; + std::vector > events; + boost::optional > nextRequest; + }; + + enum NextBlockAction { + INTERRUPT = 0, + CONTINUE, + SKIP + }; + + void handleNewBlocksPortion(std::shared_ptr context, std::deque >& events, + boost::optional >& nextRequest, std::error_code ec); + void handleTransactionOutGlobalIndicesResponse(std::shared_ptr context, crypto::hash txid, uint64_t height, + std::deque >& events, boost::optional >& nextRequest, std::error_code ec); + + void getShortChainHistory(std::list& ids); + + 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 isCoinbase, uint64_t timestamp); + void detachBlockchain(uint64_t height); + void refreshBalance(std::deque >& events); + + 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); + + const cryptonote::account_base& m_account; + INode& m_node; + std::vector& m_blockchain; + WalletTransferDetails& m_transferDetails; + WalletUnconfirmedTransactions& m_unconfirmedTransactions; + WalletUserTransactionsCache& m_transactionsCache; + + uint64_t m_actualBalance; + uint64_t m_pendingBalance; + + bool m_isStoping; +}; + +} /* namespace CryptoNote */ diff --git a/src/wallet/WalletTransactionSender.cpp b/src/wallet/WalletTransactionSender.cpp new file mode 100644 index 00000000..737a769c --- /dev/null +++ b/src/wallet/WalletTransactionSender.cpp @@ -0,0 +1,299 @@ +/* + * WalletTransactionSender.cpp + * + * Created on: 18 июня 2014 г. + * Author: milo + */ + +#include "WalletTransactionSender.h" +#include "WalletUtils.h" + +namespace { + +uint64_t countNeededMoney(uint64_t fee, const std::vector& transfers) { + uint64_t needed_money = fee; + for (auto& transfer: transfers) { + CryptoNote::throwIf(transfer.amount == 0, cryptonote::error::ZERO_DESTINATION); + CryptoNote::throwIf(transfer.amount < 0, cryptonote::error::WRONG_AMOUNT); + + needed_money += transfer.amount; + CryptoNote::throwIf(static_cast(needed_money) < transfer.amount, cryptonote::error::SUM_OVERFLOW); + } + + return needed_money; +} + +void createChangeDestinations(const cryptonote::account_public_address& address, uint64_t neededMoney, uint64_t foundMoney, cryptonote::tx_destination_entry& changeDts) { + if (neededMoney < foundMoney) { + changeDts.addr = address; + changeDts.amount = foundMoney - neededMoney; + } +} + +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) { + std::vector extraVec; + extraVec.reserve(extra.size()); + std::for_each(extra.begin(), extra.end(), [&extraVec] (const char el) { extraVec.push_back(el);}); + + bool r = cryptonote::construct_tx(keys, sources, splittedDests, extraVec, tx, unlockTimestamp); + CryptoNote::throwIf(!r, cryptonote::error::INTERNAL_WALLET_ERROR); + CryptoNote::throwIf(cryptonote::get_object_blobsize(tx) >= sizeLimit, cryptonote::error::TRANSACTION_SIZE_TOO_BIG); +} + +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()); +} + +} //namespace + +namespace CryptoNote { + +WalletTransactionSender::WalletTransactionSender(WalletUserTransactionsCache& transactionsCache, WalletTxSendingState& sendingTxsStates, + WalletTransferDetails& transferDetails, WalletUnconfirmedTransactions& unconfirmedTransactions): + 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; +} + +void WalletTransactionSender::init(cryptonote::account_keys keys) { + if (m_isInitialized) { + throw std::system_error(make_error_code(cryptonote::error::NOT_INITIALIZED)); + } + + m_keys = keys; + m_isInitialized = true; +} + +void WalletTransactionSender::stop() { + m_isStoping = true; +} + +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) + throw std::system_error(make_error_code(cryptonote::error::NOT_INITIALIZED)); + + using namespace cryptonote; + + std::shared_ptr context = std::make_shared(); + throwIf(transfers.empty(), cryptonote::error::ZERO_DESTINATION); + + TransferId firstTransferId = m_transactionsCache.insertTransfers(transfers); + + uint64_t neededMoney = countNeededMoney(fee, transfers); + + Transaction transaction; + transaction.firstTransferId = firstTransferId; + transaction.transferCount = transfers.size(); + transaction.totalAmount = neededMoney; + transaction.fee = fee; + transaction.isCoinbase = false; + transaction.timestamp = 0; + transaction.extra = extra; + transaction.blockHeight = UNCONFIRMED_TRANSACTION_HEIGHT; + + TransactionId txId = m_transactionsCache.insertTransaction(std::move(transaction)); + transactionId = txId; + m_sendingTxsStates.sending(txId); + + context->transactionId = txId; + 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; + } + + return doSendTransaction(context, events); +} + +std::shared_ptr WalletTransactionSender::makeGetRandomOutsRequest(std::shared_ptr context) { + uint64_t outsCount = context->mixIn + 1;// add one to make possible (if need) to skip real output key + std::vector amounts; + + for (auto idx: context->selectedTransfers) { + const TransferDetails& td = m_transferDetails.getTransferDetails(idx); + throwIf(td.tx.vout.size() <= td.internalOutputIndex, cryptonote::error::INTERNAL_WALLET_ERROR); + amounts.push_back(td.amount()); + } + + return std::make_shared(amounts, outsCount, context, std::bind(&WalletTransactionSender::sendTransactionRandomOutsByAmount, + this, context, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); +} + +void WalletTransactionSender::sendTransactionRandomOutsByAmount(std::shared_ptr context, std::deque >& events, + boost::optional >& nextRequest, std::error_code ec) { + if (m_isStoping) { + events.push_back(std::make_shared(context->transactionId, make_error_code(cryptonote::error::TX_CANCELLED))); + return; + } + + if (ec) { + events.push_back(std::make_shared(context->transactionId, ec)); + return; + } + + auto scanty_it = std::find_if(context->outs.begin(), context->outs.end(), [&] (cryptonote::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& out) {return out.outs.size() < context->mixIn;}); + + if (scanty_it != context->outs.end()) { + events.push_back(std::make_shared(context->transactionId, make_error_code(cryptonote::error::MIXIN_COUNT_TOO_BIG))); + return; + } + + std::shared_ptr req = doSendTransaction(context, events); + if (req) + nextRequest = req; +} + +std::shared_ptr WalletTransactionSender::doSendTransaction(std::shared_ptr context, std::deque >& events) { + if (m_isStoping) { + events.push_back(std::make_shared(context->transactionId, make_error_code(cryptonote::error::TX_CANCELLED))); + return std::shared_ptr(); + } + + try + { + Transaction& transaction = m_transactionsCache.getTransaction(context->transactionId); + + std::vector sources; + prepareInputs(context->selectedTransfers, context->outs, sources, context->mixIn); + + cryptonote::tx_destination_entry changeDts = AUTO_VAL_INIT(changeDts); + createChangeDestinations(m_keys.m_account_address, transaction.totalAmount, context->foundMoney, changeDts); + + std::vector splittedDests; + splitDestinations(transaction.firstTransferId, transaction.transferCount, changeDts, context->dustPolicy, splittedDests); + + cryptonote::transaction tx; + constructTx(m_keys, sources, splittedDests, transaction.extra, context->unlockTimestamp, m_upperTransactionSizeLimit, tx); + + fillTransactionHash(tx, transaction.hash); + + m_unconfirmedTransactions.add(tx, context->transactionId, changeDts.amount); + notifyBalanceChanged(events); + + return std::make_shared(tx, std::bind(&WalletTransactionSender::relayTransactionCallback, this, context->transactionId, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + } + catch(std::system_error& ec) { + events.push_back(std::make_shared(context->transactionId, ec.code())); + } + catch(std::exception&) { + events.push_back(std::make_shared(context->transactionId, make_error_code(cryptonote::error::INTERNAL_WALLET_ERROR))); + } + + return std::shared_ptr(); +} + +void WalletTransactionSender::relayTransactionCallback(TransactionId txId, std::deque >& events, + boost::optional >& nextRequest, std::error_code ec) { + if (m_isStoping) return; + + if (ec) { + m_sendingTxsStates.error(txId); + } else { + m_sendingTxsStates.sent(txId); + } + + events.push_back(std::make_shared(txId, ec)); +} + + +void WalletTransactionSender::splitDestinations(TransferId firstTransferId, size_t transfersCount, const cryptonote::tx_destination_entry& changeDts, + const TxDustPolicy& dustPolicy, std::vector& splittedDests) { + uint64_t dust = 0; + + digitSplitStrategy(firstTransferId, transfersCount, changeDts, dustPolicy.dustThreshold, splittedDests, dust); + + throwIf(dustPolicy.dustThreshold < dust, cryptonote::error::INTERNAL_WALLET_ERROR); + if (0 != dust && !dustPolicy.addToFee) { + splittedDests.push_back(cryptonote::tx_destination_entry(dust, dustPolicy.addrForDust)); + } +} + + +void WalletTransactionSender::digitSplitStrategy(TransferId firstTransferId, size_t transfersCount, + const cryptonote::tx_destination_entry& change_dst, uint64_t dust_threshold, + std::vector& splitted_dsts, uint64_t& dust) { + splitted_dsts.clear(); + dust = 0; + + 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)) { + throw std::system_error(make_error_code(cryptonote::error::BAD_ADDRESS)); + } + + cryptonote::decompose_amount_into_digits(de.amount, dust_threshold, + [&](uint64_t chunk) { splitted_dsts.push_back(cryptonote::tx_destination_entry(chunk, addr)); }, + [&](uint64_t a_dust) { splitted_dsts.push_back(cryptonote::tx_destination_entry(a_dust, addr)); } ); + } + + cryptonote::decompose_amount_into_digits(change_dst.amount, dust_threshold, + [&](uint64_t chunk) { splitted_dsts.push_back(cryptonote::tx_destination_entry(chunk, change_dst.addr)); }, + [&](uint64_t a_dust) { dust = a_dust; } ); +} + + +void WalletTransactionSender::prepareInputs(const std::list& selectedTransfers, std::vector& outs, + std::vector& sources, uint64_t mixIn) { + size_t i = 0; + for (size_t idx: selectedTransfers) { + sources.resize(sources.size()+1); + cryptonote::tx_source_entry& src = sources.back(); + TransferDetails& td = m_transferDetails.getTransferDetails(idx); + src.amount = td.amount(); + + //paste mixin transaction + if(outs.size()) { + outs[i].outs.sort([](const cryptonote::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry& a, const cryptonote::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry& b){return a.global_amount_index < b.global_amount_index;}); + for (auto& daemon_oe: outs[i].outs) { + if(td.globalOutputIndex == daemon_oe.global_amount_index) + continue; + cryptonote::tx_source_entry::output_entry oe; + oe.first = daemon_oe.global_amount_index; + oe.second = daemon_oe.out_key; + src.outputs.push_back(oe); + if(src.outputs.size() >= mixIn) + break; + } + } + + //paste real transaction to the random index + auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const cryptonote::tx_source_entry::output_entry& a) { return a.first >= td.globalOutputIndex; }); + + 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; + + auto interted_it = src.outputs.insert(it_to_insert, real_oe); + + src.real_out_tx_key = get_tx_pub_key_from_extra(td.tx); + src.real_output = interted_it - src.outputs.begin(); + src.real_output_in_tx_index = td.internalOutputIndex; + ++i; + } +} + +void WalletTransactionSender::notifyBalanceChanged(std::deque >& events) { + uint64_t actualBalance = m_transferDetails.countActualBalance(); + uint64_t pendingBalance = m_unconfirmedTransactions.countPendingBalance(); + pendingBalance += m_transferDetails.countPendingBalance(); + + events.push_back(std::make_shared(actualBalance)); + events.push_back(std::make_shared(pendingBalance)); +} + +} /* namespace CryptoNote */ diff --git a/src/wallet/WalletTransactionSender.h b/src/wallet/WalletTransactionSender.h new file mode 100644 index 00000000..9a51190c --- /dev/null +++ b/src/wallet/WalletTransactionSender.h @@ -0,0 +1,55 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include "INode.h" +#include "WalletSendTransactionContext.h" +#include "WalletUserTransactionsCache.h" +#include "WalletTransferDetails.h" +#include "WalletUnconfirmedTransactions.h" +#include "WalletRequest.h" + +namespace CryptoNote { + +class WalletTransactionSender +{ +public: + WalletTransactionSender(WalletUserTransactionsCache& transactionsCache, WalletTxSendingState& sendingTxsStates, WalletTransferDetails& transferDetails, + WalletUnconfirmedTransactions& unconfirmedTransactions); + + void init(cryptonote::account_keys keys); + void stop(); + + std::shared_ptr makeSendRequest(TransactionId& transactionId, std::deque >& events, const std::vector& transfers, + uint64_t fee, const std::string& extra = "", uint64_t mixIn = 0, uint64_t unlockTimestamp = 0); + +private: + std::shared_ptr makeGetRandomOutsRequest(std::shared_ptr context); + std::shared_ptr doSendTransaction(std::shared_ptr context, std::deque >& events); + void prepareInputs(const std::list& selectedTransfers, std::vector& outs, + std::vector& sources, uint64_t mixIn); + void splitDestinations(TransferId firstTransferId, size_t transfersCount, const cryptonote::tx_destination_entry& changeDts, + const TxDustPolicy& dustPolicy, std::vector& splittedDests); + void digitSplitStrategy(TransferId firstTransferId, size_t transfersCount, const cryptonote::tx_destination_entry& change_dst, uint64_t dust_threshold, + std::vector& splitted_dsts, uint64_t& dust); + void sendTransactionRandomOutsByAmount(std::shared_ptr context, std::deque >& events, + boost::optional >& nextRequest, std::error_code ec); + void relayTransactionCallback(TransactionId txId, std::deque >& events, + boost::optional >& nextRequest, std::error_code ec); + void notifyBalanceChanged(std::deque >& events); + + cryptonote::account_keys m_keys; + WalletUserTransactionsCache& m_transactionsCache; + WalletTxSendingState& m_sendingTxsStates; + WalletTransferDetails& m_transferDetails; + WalletUnconfirmedTransactions& m_unconfirmedTransactions; + uint64_t m_upperTransactionSizeLimit; + + bool m_isInitialized; + bool m_isStoping; +}; + +} /* namespace CryptoNote */ + diff --git a/src/wallet/WalletTransferDetails.cpp b/src/wallet/WalletTransferDetails.cpp new file mode 100644 index 00000000..8c05ce8b --- /dev/null +++ b/src/wallet/WalletTransferDetails.cpp @@ -0,0 +1,169 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + + +#include "WalletTransferDetails.h" + +#include +#include +#include + +#include + +#include "WalletErrors.h" + +#define DEFAULT_TX_SPENDABLE_AGE 10 + +namespace { + +template +T popRandomValue(URNG& randomGenerator, std::vector& vec) { + CHECK_AND_ASSERT_MES(!vec.empty(), T(), "Vector must be non-empty"); + + std::uniform_int_distribution distribution(0, vec.size() - 1); + size_t idx = distribution(randomGenerator); + + T res = vec[idx]; + if (idx + 1 != vec.size()) { + vec[idx] = vec.back(); + } + vec.resize(vec.size() - 1); + + return res; +} + +} + +namespace CryptoNote +{ + +WalletTransferDetails::WalletTransferDetails(const std::vector& blockchain) : m_blockchain(blockchain) { +} + +WalletTransferDetails::~WalletTransferDetails() { +} + +TransferDetails& WalletTransferDetails::getTransferDetails(size_t idx) { + return m_transfers.at(idx); +} + +void WalletTransferDetails::addTransferDetails(const TransferDetails& details) { + m_transfers.push_back(details); + size_t idx = m_transfers.size() - 1; + + m_keyImages.insert(std::make_pair(details.keyImage, idx)); +} + +bool WalletTransferDetails::getTransferDetailsIdxByKeyImage(const crypto::key_image& image, size_t& idx) { + auto it = m_keyImages.find(image); + if (it == m_keyImages.end()) + return false; + + idx = it->second; + 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 + uint64_t current_time = static_cast(time(NULL)); + if(current_time + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS >= unlockTime) + return true; + else + return false; + } + return false; +} + +bool WalletTransferDetails::isTransferUnlocked(const TransferDetails& td) const +{ + if(!isTxSpendtimeUnlocked(td.tx.unlock_time)) + return false; + + if(td.blockHeight + DEFAULT_TX_SPENDABLE_AGE > m_blockchain.size()) + return false; + + return true; +} + +uint64_t WalletTransferDetails::countActualBalance() const +{ + uint64_t amount = 0; + for (auto& transfer: m_transfers) { + if(!transfer.spent && isTransferUnlocked(transfer)) + amount += transfer.amount(); + } + + return amount; +} + +uint64_t WalletTransferDetails::countPendingBalance() const +{ + uint64_t amount = 0; + for (auto& td: m_transfers) { + if (!td.spent) + amount += td.amount(); + } + + return amount; +} + +uint64_t WalletTransferDetails::selectTransfersToSend(uint64_t neededMoney, bool addDust, uint64_t dust, std::list& selectedTransfers) { + std::vector unusedTransfers; + std::vector unusedDust; + + for (size_t i = 0; i < m_transfers.size(); ++i) { + const TransferDetails& td = m_transfers[i]; + if (!td.spent && isTransferUnlocked(td)) { + if (dust < td.amount()) + unusedTransfers.push_back(i); + else + unusedDust.push_back(i); + } + } + + std::default_random_engine randomGenerator(crypto::rand()); + bool selectOneDust = addDust && !unusedDust.empty(); + uint64_t foundMoney = 0; + while (foundMoney < neededMoney && (!unusedTransfers.empty() || !unusedDust.empty())) { + size_t idx; + if (selectOneDust) { + idx = popRandomValue(randomGenerator, unusedDust); + selectOneDust = false; + } + else + { + idx = !unusedTransfers.empty() ? popRandomValue(randomGenerator, unusedTransfers) : popRandomValue(randomGenerator, unusedDust); + } + + selectedTransfers.push_back(idx); + foundMoney += m_transfers[idx].amount(); + } + + return foundMoney; +} + +void WalletTransferDetails::detachTransferDetails(size_t height) { + auto it = std::find_if(m_transfers.begin(), m_transfers.end(), [&](const TransferDetails& td){return td.blockHeight >= height;}); + size_t start = it - m_transfers.begin(); + + for(size_t i = start; i!= m_transfers.size();i++) { + auto ki = m_keyImages.find(m_transfers[i].keyImage); + if(ki == m_keyImages.end()) throw std::system_error(make_error_code(cryptonote::error::INTERNAL_WALLET_ERROR)); + + m_keyImages.erase(ki); + } + m_transfers.erase(it, m_transfers.end()); +} + +} /* namespace CryptoNote */ diff --git a/src/wallet/WalletTransferDetails.h b/src/wallet/WalletTransferDetails.h new file mode 100644 index 00000000..7dbdae12 --- /dev/null +++ b/src/wallet/WalletTransferDetails.h @@ -0,0 +1,82 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include + +#include "cryptonote_core/cryptonote_format_utils.h" +#include "IWallet.h" + +namespace CryptoNote { + +struct TransferDetails +{ + uint64_t blockHeight; + cryptonote::transaction tx; + size_t internalOutputIndex; + uint64_t globalOutputIndex; + bool spent; + crypto::key_image keyImage; + + uint64_t amount() const + { + return tx.vout[internalOutputIndex].amount; + } +}; + +class WalletTransferDetails +{ +public: + WalletTransferDetails(const std::vector& blockchain); + ~WalletTransferDetails(); + + TransferDetails& getTransferDetails(size_t idx); + void addTransferDetails(const TransferDetails& details); + bool getTransferDetailsIdxByKeyImage(const crypto::key_image& image, size_t& idx); + + uint64_t countActualBalance() const; + uint64_t countPendingBalance() const; + bool isTransferUnlocked(const TransferDetails& td) const; + + uint64_t selectTransfersToSend(uint64_t neededMoney, bool addDust, uint64_t dust, std::list& selectedTransfers); + + void detachTransferDetails(size_t height); + + template + void save(Archive& ar, bool saveCache) const; + + template + void load(Archive& ar); + +private: + bool isTxSpendtimeUnlocked(uint64_t unlock_time) const; + + typedef std::vector TransferContainer; + TransferContainer m_transfers; + + typedef std::unordered_map KeyImagesContainer; + KeyImagesContainer m_keyImages; + + const std::vector& m_blockchain; +}; + +template +void WalletTransferDetails::save(Archive& ar, bool saveCache) const +{ + const TransferContainer& transfers = saveCache ? m_transfers : TransferContainer(); + const KeyImagesContainer& keyImages = saveCache ? m_keyImages : KeyImagesContainer(); + + ar << transfers; + ar << keyImages; +} + +template +void WalletTransferDetails::load(Archive& ar) +{ + ar >> m_transfers; + ar >> m_keyImages; +} + +} //namespace CryptoNote diff --git a/src/wallet/WalletTxSendingState.cpp b/src/wallet/WalletTxSendingState.cpp new file mode 100644 index 00000000..fe1fda6c --- /dev/null +++ b/src/wallet/WalletTxSendingState.cpp @@ -0,0 +1,29 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "WalletTxSendingState.h" + +namespace CryptoNote { + +void WalletTxSendingState::sending(TransactionId id) { + m_states[id] = SENDING; +} + +void WalletTxSendingState::sent(TransactionId id) { + m_states.erase(id); +} + +void WalletTxSendingState::error(TransactionId id) { + m_states[id] = ERRORED; +} + +WalletTxSendingState::State WalletTxSendingState::state(TransactionId id) { + auto it = m_states.find(id); + + if (it == m_states.end()) + return NOT_FOUND; + + return it->second; +} +} //namespace CryptoNote diff --git a/src/wallet/WalletTxSendingState.h b/src/wallet/WalletTxSendingState.h new file mode 100644 index 00000000..65ba7d30 --- /dev/null +++ b/src/wallet/WalletTxSendingState.h @@ -0,0 +1,33 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include "IWallet.h" + +#include +#include + +namespace CryptoNote { + +class WalletTxSendingState +{ +public: + enum State + { + SENDING, + ERRORED, + NOT_FOUND + }; + + void sending(TransactionId id); + void sent(TransactionId id); + void error(TransactionId id); + State state(TransactionId id); + +private: + std::map m_states; +}; + +} //namespace CryptoNote diff --git a/src/wallet/WalletUnconfirmedTransactions.cpp b/src/wallet/WalletUnconfirmedTransactions.cpp new file mode 100644 index 00000000..53a632cb --- /dev/null +++ b/src/wallet/WalletUnconfirmedTransactions.cpp @@ -0,0 +1,45 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "WalletUnconfirmedTransactions.h" +#include "cryptonote_core/cryptonote_format_utils.h" + +namespace CryptoNote { + +bool WalletUnconfirmedTransactions::findTransactionId(const crypto::hash& hash, TransactionId& id) { + auto it = m_unconfirmedTxs.find(hash); + + if(it == m_unconfirmedTxs.end()) + return false; + + id = it->second.transactionId; + + return true; +} + +void WalletUnconfirmedTransactions::erase(const crypto::hash& hash) { + m_unconfirmedTxs.erase(hash); +} + +void WalletUnconfirmedTransactions::add(const cryptonote::transaction& tx, + TransactionId transactionId, uint64_t change_amount) { + UnconfirmedTransferDetails& utd = m_unconfirmedTxs[cryptonote::get_transaction_hash(tx)]; + + utd.change = change_amount; + utd.sentTime = time(NULL); + utd.tx = tx; + utd.transactionId = transactionId; +} + +uint64_t WalletUnconfirmedTransactions::countPendingBalance() const +{ + uint64_t amount = 0; + + for (auto& utx: m_unconfirmedTxs) + amount+= utx.second.change; + + return amount; +} + +} /* namespace CryptoNote */ diff --git a/src/wallet/WalletUnconfirmedTransactions.h b/src/wallet/WalletUnconfirmedTransactions.h new file mode 100644 index 00000000..bfd99634 --- /dev/null +++ b/src/wallet/WalletUnconfirmedTransactions.h @@ -0,0 +1,59 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include + +#include + +#include "IWallet.h" +#include "crypto/hash.h" +#include "cryptonote_core/cryptonote_basic.h" + +namespace CryptoNote { + +struct UnconfirmedTransferDetails +{ + cryptonote::transaction tx; + uint64_t change; + time_t sentTime; + TransactionId transactionId; +}; + +class WalletUnconfirmedTransactions +{ +public: + template + void save(Archive& ar, bool saveCache) const; + + template + void load(Archive& ar); + + 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); + + uint64_t countPendingBalance() const; + +private: + typedef std::unordered_map UnconfirmedTxsContainer; + UnconfirmedTxsContainer m_unconfirmedTxs; +}; + +template +void WalletUnconfirmedTransactions::save(Archive& ar, bool saveCache) const +{ + const UnconfirmedTxsContainer& unconfirmedTxs = saveCache ? m_unconfirmedTxs : UnconfirmedTxsContainer(); + ar << unconfirmedTxs; +} + +template +void WalletUnconfirmedTransactions::load(Archive& ar) +{ + ar >> m_unconfirmedTxs; +} + +} // namespace CryptoNote + diff --git a/src/wallet/WalletUserTransactionsCache.cpp b/src/wallet/WalletUserTransactionsCache.cpp new file mode 100644 index 00000000..f0adbc98 --- /dev/null +++ b/src/wallet/WalletUserTransactionsCache.cpp @@ -0,0 +1,166 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "WalletUserTransactionsCache.h" + +#include + +namespace { +bool hashesEqual(const CryptoNote::TransactionHash& h1, const crypto::hash& h2) { + return !memcmp(static_cast(h1.data()), static_cast(&h2), h1.size()); +} +} + +namespace CryptoNote { + +size_t WalletUserTransactionsCache::getTransactionCount() const +{ + return m_transactions.size(); +} + +size_t WalletUserTransactionsCache::getTransferCount() const +{ + return m_transfers.size(); +} + +TransactionId WalletUserTransactionsCache::findTransactionByTransferId(TransferId transferId) const +{ + TransactionId id; + for (id = 0; id < m_transactions.size(); ++id) { + const Transaction& tx = m_transactions[id]; + + if (tx.firstTransferId == INVALID_TRANSFER_ID || tx.transferCount == 0) + continue; + + if (transferId >= tx.firstTransferId && transferId < (tx.firstTransferId + tx.transferCount)) + break; + } + + if (id == m_transactions.size()) + return INVALID_TRANSACTION_ID; + + return id; +} + +bool WalletUserTransactionsCache::getTransaction(TransactionId transactionId, Transaction& transaction) const +{ + if (transactionId >= m_transactions.size()) + return false; + + transaction = m_transactions[transactionId]; + + return true; +} + +bool WalletUserTransactionsCache::getTransfer(TransferId transferId, Transfer& transfer) const +{ + if (transferId >= m_transfers.size()) + return false; + + transfer = m_transfers[transferId]; + + return true; +} + +TransactionId WalletUserTransactionsCache::insertTransaction(Transaction&& 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); }); + + if (it == m_transactions.end()) + return CryptoNote::INVALID_TRANSACTION_ID; + + return std::distance(m_transactions.begin(), it); +} + +void WalletUserTransactionsCache::detachTransactions(uint64_t height) { + for (size_t id = 0; id < m_transactions.size(); ++id) { + Transaction& tx = m_transactions[id]; + if (tx.blockHeight >= height) { + tx.blockHeight = UNCONFIRMED_TRANSACTION_HEIGHT; + tx.timestamp = 0; + } + } +} + +Transaction& WalletUserTransactionsCache::getTransaction(TransactionId transactionId) { + return m_transactions.at(transactionId); +} + +void WalletUserTransactionsCache::getGoodItems(bool saveDetailed, UserTransactions& transactions, UserTransfers& transfers) { + size_t offset = 0; + + for (size_t txId = 0; txId < m_transactions.size(); ++txId) { + WalletTxSendingState::State state = m_sendingTxsStates.state(txId); + bool isGood = state != WalletTxSendingState::ERRORED; + + if (isGood) { + getGoodTransaction(txId, offset, saveDetailed, transactions, transfers); + } + else + { + const Transaction& t = m_transactions[txId]; + if (t.firstTransferId != INVALID_TRANSFER_ID) + offset += t.transferCount; + } + } +} + +void WalletUserTransactionsCache::getGoodTransaction(TransactionId txId, size_t offset, bool saveDetailed, UserTransactions& transactions, UserTransfers& transfers) { + transactions.push_back(m_transactions[txId]); + Transaction& tx = transactions.back(); + + if (!saveDetailed) { + tx.firstTransferId = INVALID_TRANSFER_ID; + tx.transferCount = 0; + + return; + } + + if (tx.firstTransferId == INVALID_TRANSFER_ID) { + return; + } + + UserTransfers::const_iterator first = m_transfers.begin() + tx.firstTransferId; + UserTransfers::const_iterator last = first + tx.transferCount; + + tx.firstTransferId -= offset; + + std::copy(first, last, std::back_inserter(transfers)); +} + +void WalletUserTransactionsCache::getGoodTransfers(UserTransfers& transfers) { + for (size_t txId = 0; txId < m_transactions.size(); ++txId) { + WalletTxSendingState::State state = m_sendingTxsStates.state(txId); + + if (state != WalletTxSendingState::ERRORED) { + getTransfersByTx(txId, transfers); + } + } +} + +void WalletUserTransactionsCache::getTransfersByTx(TransactionId id, UserTransfers& transfers) { + const Transaction& tx = m_transactions[id]; + + if (tx.firstTransferId != INVALID_TRANSFER_ID) { + UserTransfers::const_iterator first = m_transfers.begin() + tx.firstTransferId; + UserTransfers::const_iterator last = first + tx.transferCount; + std::copy(first, last, std::back_inserter(transfers)); + } +} + +TransferId WalletUserTransactionsCache::insertTransfers(const std::vector& transfers) { + std::copy(transfers.begin(), transfers.end(), std::back_inserter(m_transfers)); + return m_transfers.size() - transfers.size(); +} + +Transfer& WalletUserTransactionsCache::getTransfer(TransferId transferId) { + return m_transfers.at(transferId); +} + +} //namespace CryptoNote + diff --git a/src/wallet/WalletUserTransactionsCache.h b/src/wallet/WalletUserTransactionsCache.h new file mode 100644 index 00000000..48971bfd --- /dev/null +++ b/src/wallet/WalletUserTransactionsCache.h @@ -0,0 +1,77 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include "crypto/hash.h" +#include "IWallet.h" +#include "WalletTxSendingState.h" + +namespace CryptoNote { + +class WalletUserTransactionsCache +{ +public: + WalletUserTransactionsCache(WalletTxSendingState& states) : m_sendingTxsStates(states) {} + + template + void save(Archive& ar, bool saveDetailed, bool saveCache); + + template + void load(Archive& ar); + + size_t getTransactionCount() const; + size_t getTransferCount() const; + + TransactionId findTransactionByTransferId(TransferId transferId) const; + + bool getTransaction(TransactionId transactionId, Transaction& transaction) const; + Transaction& getTransaction(TransactionId transactionId); + bool getTransfer(TransferId transferId, Transfer& transfer) const; + Transfer& getTransfer(TransferId transferId); + + TransactionId insertTransaction(Transaction&& transaction); + TransferId insertTransfers(const std::vector& transfers); + + TransactionId findTransactionByHash(const crypto::hash& hash); + void detachTransactions(uint64_t height); + +private: + typedef std::vector UserTransfers; + 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); + + void getGoodTransfers(UserTransfers& transfers); + void getTransfersByTx(TransactionId id, UserTransfers& transfers); + + UserTransactions m_transactions; + UserTransfers m_transfers; + + WalletTxSendingState& m_sendingTxsStates; +}; + +template +void WalletUserTransactionsCache::load(Archive& ar) { + ar >> m_transactions; + ar >> m_transfers; +} + +template +void WalletUserTransactionsCache::save(Archive& ar, bool saveDetailed, bool saveCache) { + UserTransactions txsToSave; + UserTransfers transfersToSave; + + if (saveCache) { + getGoodItems(saveDetailed, txsToSave, transfersToSave); + } else { + if (saveDetailed) getGoodTransfers(transfersToSave); + } + + ar << txsToSave; + ar << transfersToSave; +} + +} //namespace CryptoNote diff --git a/src/wallet/WalletUtils.h b/src/wallet/WalletUtils.h new file mode 100644 index 00000000..46d57793 --- /dev/null +++ b/src/wallet/WalletUtils.h @@ -0,0 +1,19 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include +#include "WalletErrors.h" + +namespace CryptoNote { + +inline void throwIf(bool expr, cryptonote::error::WalletErrorCodes ec) +{ + if (expr) + throw std::system_error(make_error_code(ec)); +} + +} //namespace CryptoNote + diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 111b7611..7e57d601 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -12,6 +12,7 @@ using namespace epee; #include "wallet2.h" #include "cryptonote_core/cryptonote_format_utils.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" @@ -387,7 +388,8 @@ bool wallet2::store_keys(const std::string& keys_file_name, const std::string& p wallet2::keys_file_data keys_file_data = boost::value_initialized(); crypto::chacha8_key key; - crypto::generate_chacha8_key(password, key); + crypto::cn_context cn_context; + crypto::generate_chacha8_key(cn_context, password, key); std::string cipher; cipher.resize(account_data.size()); keys_file_data.iv = crypto::rand(); @@ -422,7 +424,8 @@ void wallet2::load_keys(const std::string& keys_file_name, const std::string& pa THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + keys_file_name + '\"'); crypto::chacha8_key key; - crypto::generate_chacha8_key(password, key); + crypto::cn_context cn_context; + crypto::generate_chacha8_key(cn_context, password, key); std::string account_data; account_data.resize(keys_file_data.account_data.size()); crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); @@ -465,6 +468,20 @@ void wallet2::wallet_exists(const std::string& file_path, bool& keys_file_exists wallet_file_exists = boost::filesystem::exists(wallet_file, ignore); } //---------------------------------------------------------------------------------------------------- +bool wallet2::parse_payment_id(const std::string& payment_id_str, crypto::hash& payment_id) { + cryptonote::blobdata payment_id_data; + if (!epee::string_tools::parse_hexstr_to_binbuff(payment_id_str, payment_id_data)) { + return false; + } + + if (sizeof(crypto::hash) != payment_id_data.size()) { + return false; + } + + payment_id = *reinterpret_cast(payment_id_data.data()); + return true; +} +//---------------------------------------------------------------------------------------------------- bool wallet2::prepare_file_names(const std::string& file_path) { do_prepare_file_names(file_path, m_keys_file, m_wallet_file); @@ -663,7 +680,7 @@ 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) { - transfer(dsts, fake_outputs_count, unlock_time, fee, extra, detail::digit_split_strategy, tx_dust_policy(fee), tx); + transfer(dsts, fake_outputs_count, unlock_time, fee, extra, detail::digit_split_strategy, tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); } //---------------------------------------------------------------------------------------------------- void wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index d2016f69..a2c961e8 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -145,6 +145,7 @@ namespace tools } static void wallet_exists(const std::string& file_path, bool& keys_file_exists, bool& wallet_file_exists); + static bool parse_payment_id(const std::string& payment_id_str, crypto::hash& payment_id); private: bool store_keys(const std::string& keys_file_name, const std::string& password); diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index f1766c3b..7c4c7e3d 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -73,13 +73,10 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx) { - std::vector dsts; - for (auto it = req.destinations.begin(); it != req.destinations.end(); it++) - { + 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 (!get_account_address_from_str(de.addr, it->address)) { er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; er.message = std::string("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS: ") + it->address; return false; @@ -87,27 +84,41 @@ namespace tools de.amount = it->amount; dsts.push_back(de); } - try - { + + std::vector extra; + if (!req.payment_id.empty()) { + std::string payment_id_str = req.payment_id; + + crypto::hash payment_id; + if (!wallet2::parse_payment_id(payment_id_str, payment_id)) { + er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; + er.message = "Payment id has invalid format: \"" + payment_id_str + "\", expected 64-character string"; + return false; + } + + std::string extra_nonce; + cryptonote::set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); + if (!cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce)) { + er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; + er.message = "Something went wrong with payment_id. Please check its format: \"" + payment_id_str + "\", expected 64-character string"; + return false; + } + } + + try { cryptonote::transaction tx; - m_wallet.transfer(dsts, req.mixin, req.unlock_time, req.fee, std::vector(), 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; - } - catch (const tools::error::daemon_busy& e) - { + } catch (const tools::error::daemon_busy& e) { er.code = WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY; er.message = e.what(); return false; - } - catch (const std::exception& e) - { + } catch (const std::exception& e) { er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; er.message = e.what(); return false; - } - catch (...) - { + } catch (...) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR"; return false; diff --git a/src/wallet/wallet_rpc_server_commans_defs.h b/src/wallet/wallet_rpc_server_commans_defs.h index b99d92ca..217d7a21 100644 --- a/src/wallet/wallet_rpc_server_commans_defs.h +++ b/src/wallet/wallet_rpc_server_commans_defs.h @@ -24,8 +24,8 @@ namespace wallet_rpc struct response { - uint64_t balance; - uint64_t unlocked_balance; + uint64_t balance; + uint64_t unlocked_balance; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(balance) @@ -52,12 +52,14 @@ namespace wallet_rpc uint64_t fee; uint64_t mixin; uint64_t unlock_time; + std::string payment_id; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(destinations) KV_SERIALIZE(fee) KV_SERIALIZE(mixin) KV_SERIALIZE(unlock_time) + KV_SERIALIZE(payment_id) END_KV_SERIALIZE_MAP() }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 855b4d80..35beea14 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,7 +1,7 @@ add_definitions(-DSTATICLIB) add_subdirectory(gtest) -include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR}) +include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR} ../version) file(GLOB_RECURSE CORE_TESTS core_tests/*) file(GLOB_RECURSE CRYPTO_TESTS crypto/*) @@ -19,6 +19,8 @@ source_group(unit_tests FILES ${UNIT_TESTS}) # add_subdirectory(daemon_tests) +add_library(coretests_lib ${CORE_TESTS}) + add_executable(coretests ${CORE_TESTS}) add_executable(crypto-tests ${CRYPTO_TESTS}) add_executable(difficulty-tests difficulty/difficulty.cpp) @@ -38,16 +40,24 @@ target_link_libraries(functional_tests cryptonote_core wallet common crypto ${Bo 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 gtest_main ${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}) + + +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}) + + 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") endif() -add_custom_target(tests DEPENDS coretests difficulty hash performance_tests core_proxy unit_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 PROPERTY FOLDER "tests") +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") 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/core_proxy/core_proxy.cpp b/tests/core_proxy/core_proxy.cpp index caf4bf83..d6180407 100644 --- a/tests/core_proxy/core_proxy.cpp +++ b/tests/core_proxy/core_proxy.cpp @@ -41,7 +41,7 @@ int main(int argc, char* argv[]) #ifdef WIN32 _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); -#endif +#endif TRY_ENTRY(); @@ -51,8 +51,8 @@ int main(int argc, char* argv[]) //set up logging options log_space::get_set_log_detalisation_level(true, LOG_LEVEL_2); //log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL); - log_space::log_singletone::add_logger(LOGGER_FILE, - log_space::log_singletone::get_default_log_file().c_str(), + log_space::log_singletone::add_logger(LOGGER_FILE, + log_space::log_singletone::get_default_log_file().c_str(), log_space::log_singletone::get_default_log_folder().c_str()); @@ -98,14 +98,14 @@ int main(int argc, char* argv[]) //initialize core here LOG_PRINT_L0("Initializing proxy core..."); res = pr_core.init(vm); - CHECK_AND_ASSERT_MES(res, 1, "Failed to initialize core"); + CHECK_AND_ASSERT_MES(res, 1, "Failed to initialize core"); LOG_PRINT_L0("Core initialized OK"); LOG_PRINT_L0("Starting p2p net loop..."); p2psrv.run(); LOG_PRINT_L0("p2p net loop stopped"); - //deinitialize components + //deinitialize components LOG_PRINT_L0("Deinitializing core..."); pr_core.deinit(); LOG_PRINT_L0("Deinitializing cryptonote_protocol..."); @@ -172,7 +172,7 @@ bool tests::proxy_core::handle_incoming_block(const cryptonote::blobdata& block_ crypto::hash lh; cout << "BLOCK" << endl << endl; cout << (h = get_block_hash(b)) << endl; - cout << (lh = get_block_longhash(b, 0)) << 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; @@ -200,7 +200,7 @@ 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_genesis, 0), m_genesis, block_to_blob(m_genesis)); + add_block(h, get_block_longhash(m_cn_context, m_genesis, 0), m_genesis, block_to_blob(m_genesis)); return true; } diff --git a/tests/core_proxy/core_proxy.h b/tests/core_proxy/core_proxy.h index d5be53f1..0d707089 100644 --- a/tests/core_proxy/core_proxy.h +++ b/tests/core_proxy/core_proxy.h @@ -34,9 +34,11 @@ namespace tests crypto::hash m_lastblk; std::list txes; + 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); - + public: void on_synchronized(){} diff --git a/tests/core_tests/chain_switch_1.cpp b/tests/core_tests/chain_switch_1.cpp index 8e9a2c1d..a08cabeb 100644 --- a/tests/core_tests/chain_switch_1.cpp +++ b/tests/core_tests/chain_switch_1.cpp @@ -120,8 +120,7 @@ bool gen_chain_switch_1::check_split_not_switched(cryptonote::core& c, size_t ev CHECK_EQ(MK_COINS(3), get_balance(m_recipient_account_4, chain, mtx)); std::list tx_pool; - r = c.get_pool_transactions(tx_pool); - CHECK_TEST_CONDITION(r); + c.get_pool_transactions(tx_pool); CHECK_EQ(1, tx_pool.size()); std::vector tx_outs; @@ -170,8 +169,7 @@ bool gen_chain_switch_1::check_split_switched(cryptonote::core& c, size_t ev_ind CHECK_EQ(MK_COINS(16), get_balance(m_recipient_account_4, chain, mtx)); std::list tx_pool; - r = c.get_pool_transactions(tx_pool); - CHECK_TEST_CONDITION(r); + 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/chaingen.cpp b/tests/core_tests/chaingen.cpp index 124800db..c7ae28b9 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -153,7 +153,8 @@ bool test_generator::construct_block(cryptonote::block& blk, uint64_t height, co // Nonce search... blk.nonce = 0; - while (!miner::find_nonce_for_given_block(blk, get_test_difficulty(), height)) + 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); @@ -479,7 +480,8 @@ 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; - while (!miner::find_nonce_for_given_block(blk, diffic, height)) + crypto::cn_context context; + while (!miner::find_nonce_for_given_block(context, blk, diffic, height)) blk.timestamp++; } diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index 3d47edee..f17ae289 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -461,7 +461,7 @@ inline bool do_replay_events(std::vector& events) cryptonote::cryptonote_protocol_stub pr; //TODO: stub only for this kind of test, make real validation of relayed objects cryptonote::core c(&pr); - if (!c.init(vm)) + if (!c.init(vm, false)) { std::cout << concolor::magenta << "Failed to init core" << concolor::normal << std::endl; return false; @@ -643,4 +643,4 @@ inline bool do_replay_file(const std::string& filename) #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 ((uint64_t)1000000) // pow(10, 6) +#define TESTS_DEFAULT_FEE (MINIMUM_FEE) diff --git a/tests/core_tests/double_spend.h b/tests/core_tests/double_spend.h index e43cc2ed..8242ba56 100644 --- a/tests/core_tests/double_spend.h +++ b/tests/core_tests/double_spend.h @@ -99,7 +99,7 @@ struct gen_double_spend_in_alt_chain_in_different_blocks : public gen_double_spe class gen_double_spend_in_different_chains : public test_chain_unit_base { public: - static const uint64_t send_amount = MK_COINS(17); + static const uint64_t send_amount = MK_COINS(31); static const size_t expected_blockchain_height = 4 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; gen_double_spend_in_different_chains(); diff --git a/tests/functional_tests/transactions_flow_test.cpp b/tests/functional_tests/transactions_flow_test.cpp index 4c6d5b07..fe26d04b 100644 --- a/tests/functional_tests/transactions_flow_test.cpp +++ b/tests/functional_tests/transactions_flow_test.cpp @@ -52,7 +52,7 @@ bool do_send_money(tools::wallet2& w1, tools::wallet2& w2, size_t mix_in_factor, try { - w1.transfer(dsts, mix_in_factor, 0, DEFAULT_FEE, std::vector(), tools::detail::null_split_strategy, tools::tx_dust_policy(DEFAULT_FEE), tx); + w1.transfer(dsts, mix_in_factor, 0, MINIMUM_FEE, std::vector(), tools::detail::null_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); return true; } catch (const std::exception&) @@ -157,7 +157,7 @@ bool transactions_flow_test(std::string& working_folder, 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 - DEFAULT_FEE, tx_s, 50); + bool r = do_send_money(w1, w1, 0, td.m_tx.vout[td.m_internal_output_index].amount - MINIMUM_FEE, 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) @@ -185,7 +185,7 @@ 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 + DEFAULT_FEE) + while(w1.unlocked_balance() < amount_to_tx + MINIMUM_FEE) { misc_utils::sleep_no_w(1000); LOG_PRINT_L0("not enough money, waiting for cashback or mining"); diff --git a/tests/hash/main.cpp b/tests/hash/main.cpp index 6c898c9f..da5a2e22 100644 --- a/tests/hash/main.cpp +++ b/tests/hash/main.cpp @@ -16,23 +16,30 @@ using namespace std; using namespace crypto; typedef crypto::hash chash; +cn_context *context; + +extern "C" { + PUSH_WARNINGS DISABLE_VS_WARNINGS(4297) -extern "C" { static void hash_tree(const void *data, size_t length, char *hash) { if ((length & 31) != 0) { throw ios_base::failure("Invalid input length for tree_hash"); } tree_hash((const char (*)[32]) data, length >> 5, hash); } -} POP_WARNINGS + static void slow_hash(const void *data, size_t length, char *hash) { + cn_slow_hash(*context, data, length, *reinterpret_cast(hash)); + } +} + extern "C" typedef void hash_f(const void *, size_t, char *); struct hash_func { const string name; hash_f &f; -} hashes[] = {{"fast", cn_fast_hash}, {"slow", cn_slow_hash}, {"tree", hash_tree}, +} hashes[] = {{"fast", cn_fast_hash}, {"slow", slow_hash}, {"tree", hash_tree}, {"extra-blake", hash_extra_blake}, {"extra-groestl", hash_extra_groestl}, {"extra-jh", hash_extra_jh}, {"extra-skein", hash_extra_skein}}; @@ -58,6 +65,9 @@ int main(int argc, char *argv[]) { break; } } + if (f == slow_hash) { + context = new cn_context(); + } input.open(argv[2], ios_base::in); for (;;) { ++test; diff --git a/tests/io.h b/tests/io.h index 64f2bae7..e3efa62c 100644 --- a/tests/io.h +++ b/tests/io.h @@ -80,7 +80,7 @@ inline void get(std::istream &input, std::vector &res) { } } -#if !defined(_MSC_VER) +#if !defined(_MSC_VER) || _MSC_VER >= 1800 template typename std::enable_if<(sizeof...(TT) > 0), void>::type diff --git a/tests/node_rpc_proxy_test/node_rpc_proxy_test.cpp b/tests/node_rpc_proxy_test/node_rpc_proxy_test.cpp new file mode 100644 index 00000000..e3aa7037 --- /dev/null +++ b/tests/node_rpc_proxy_test/node_rpc_proxy_test.cpp @@ -0,0 +1,132 @@ +#include +#include + +#include "include_base_utils.h" + +#include "node_rpc_proxy/NodeRpcProxy.h" + +using namespace cryptonote; +using namespace CryptoNote; + + +class NodeObserver : public INodeObserver { +public: + NodeObserver(const std::string& name, NodeRpcProxy& nodeProxy) + : m_name(name) + , m_nodeProxy(nodeProxy) { + } + + virtual ~NodeObserver() { + } + + virtual void peerCountUpdated(size_t count) { + LOG_PRINT_L0('[' << m_name << "] peerCountUpdated " << count << " = " << m_nodeProxy.getPeerCount()); + } + + virtual void localBlockchainUpdated(uint64_t height) { + LOG_PRINT_L0('[' << m_name << "] localBlockchainUpdated " << height << " = " << m_nodeProxy.getLastLocalBlockHeight()); + + std::vector amounts; + amounts.push_back(100000000); + auto outs = std::make_shared>(); + m_nodeProxy.getRandomOutsByAmounts(std::move(amounts), 10, *outs.get(), [outs](std::error_code ec) { + if (!ec) { + if (1 == outs->size() && 10 == (*outs)[0].outs.size()) { + LOG_PRINT_L0("getRandomOutsByAmounts called successfully"); + } else { + LOG_PRINT_RED_L0("getRandomOutsByAmounts returned invalid result"); + } + } else { + LOG_PRINT_RED_L0("failed to call getRandomOutsByAmounts: " << ec.message() << ':' << ec.value()); + } + }); + } + + virtual void lastKnownBlockHeightUpdated(uint64_t height) { + LOG_PRINT_L0('[' << m_name << "] lastKnownBlockHeightUpdated " << height << " = " << m_nodeProxy.getLastKnownBlockHeight()); + } + +private: + std::string m_name; + NodeRpcProxy& m_nodeProxy; +}; + +int main(int argc, const char** argv) { + //set up logging options + epee::log_space::get_set_log_detalisation_level(true, LOG_LEVEL_2); + epee::log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL); + + NodeRpcProxy nodeProxy("127.0.0.1", 18081); + + NodeObserver observer1("obs1", nodeProxy); + NodeObserver observer2("obs2", nodeProxy); + + nodeProxy.addObserver(&observer1); + nodeProxy.addObserver(&observer2); + + nodeProxy.init([](std::error_code ec) { + if (ec) { + LOG_PRINT_RED_L0("init error: " << ec.message() << ':' << ec.value()); + } else { + LOG_PRINT_GREEN("initialized", LOG_LEVEL_0); + } + }); + + //nodeProxy.init([](std::error_code ec) { + // if (ec) { + // LOG_PRINT_RED_L0("init error: " << ec.message() << ':' << ec.value()); + // } else { + // LOG_PRINT_GREEN("initialized", LOG_LEVEL_0); + // } + //}); + + std::this_thread::sleep_for(std::chrono::seconds(5)); + if (nodeProxy.shutdown()) { + LOG_PRINT_GREEN("shutdown", LOG_LEVEL_0); + } else { + LOG_PRINT_RED_L0("shutdown error"); + } + + nodeProxy.init([](std::error_code ec) { + if (ec) { + LOG_PRINT_RED_L0("init error: " << ec.message() << ':' << ec.value()); + } else { + LOG_PRINT_GREEN("initialized", LOG_LEVEL_0); + } + }); + + std::this_thread::sleep_for(std::chrono::seconds(5)); + if (nodeProxy.shutdown()) { + LOG_PRINT_GREEN("shutdown", LOG_LEVEL_0); + } else { + LOG_PRINT_RED_L0("shutdown error"); + } + + cryptonote::transaction tx; + nodeProxy.relayTransaction(tx, [](std::error_code ec) { + if (!ec) { + LOG_PRINT_L0("relayTransaction called successfully"); + } else { + LOG_PRINT_RED_L0("failed to call relayTransaction: " << ec.message() << ':' << ec.value()); + } + }); + + nodeProxy.init([](std::error_code ec) { + if (ec) { + LOG_PRINT_RED_L0("init error: " << ec.message() << ':' << ec.value()); + } else { + LOG_PRINT_GREEN("initialized", LOG_LEVEL_0); + } + }); + + std::this_thread::sleep_for(std::chrono::seconds(5)); + nodeProxy.relayTransaction(tx, [](std::error_code ec) { + if (!ec) { + LOG_PRINT_L0("relayTransaction called successfully"); + } else { + LOG_PRINT_RED_L0("failed to call relayTransaction: " << ec.message() << ':' << ec.value()); + } + }); + + std::this_thread::sleep_for(std::chrono::seconds(60)); +} diff --git a/tests/performance_tests/cn_slow_hash.h b/tests/performance_tests/cn_slow_hash.h index ec001326..14c4a9ba 100644 --- a/tests/performance_tests/cn_slow_hash.h +++ b/tests/performance_tests/cn_slow_hash.h @@ -35,11 +35,12 @@ public: bool test() { crypto::hash hash; - crypto::cn_slow_hash(&m_data, sizeof(m_data), hash); + crypto::cn_slow_hash(m_context, &m_data, sizeof(m_data), hash); return hash == m_expected_hash; } private: data_t m_data; crypto::hash m_expected_hash; + crypto::cn_context m_context; }; diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp index 72ee2ca6..2c65f1c2 100644 --- a/tests/performance_tests/main.cpp +++ b/tests/performance_tests/main.cpp @@ -4,6 +4,7 @@ #include "performance_tests.h" #include "performance_utils.h" +#include "crypto/hash.h" // tests #include "construct_tx.h" diff --git a/tests/unit_tests/INodeStubs.cpp b/tests/unit_tests/INodeStubs.cpp new file mode 100644 index 00000000..fbc135c7 --- /dev/null +++ b/tests/unit_tests/INodeStubs.cpp @@ -0,0 +1,128 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "INodeStubs.h" +#include "cryptonote_core/cryptonote_format_utils.h" +#include "wallet/WalletErrors.h" + +#include +#include +#include +#include + +#include "crypto/crypto.h" + +void INodeTrivialRefreshStub::getNewBlocks(std::list&& knownBlockIds, std::list& newBlocks, uint64_t& startHeight, const Callback& callback) +{ + std::thread task(&INodeTrivialRefreshStub::doGetNewBlocks, this, knownBlockIds, std::ref(newBlocks), std::ref(startHeight), callback); + task.detach(); +} + +void INodeTrivialRefreshStub::doGetNewBlocks(std::list knownBlockIds, std::list& newBlocks, uint64_t& startHeight, const Callback& callback) +{ + auto& blockchain = m_blockchainGenerator.getBlockchain(); + + startHeight = m_lastHeight; + + for (; m_lastHeight < blockchain.size(); ++m_lastHeight) + { + cryptonote::block_complete_entry e; + e.block = cryptonote::t_serializable_object_to_blob(blockchain[m_lastHeight]); + + for (auto hash: blockchain[m_lastHeight].tx_hashes) + { + cryptonote::transaction tx; + if (!m_blockchainGenerator.getTransactionByHash(hash, tx)) + continue; + + e.txs.push_back(t_serializable_object_to_blob(tx)); + } + + newBlocks.push_back(e); + } + + m_lastHeight = blockchain.size() - 1; + + callback(std::error_code()); +} + +void INodeTrivialRefreshStub::getTransactionOutsGlobalIndices(const crypto::hash& transactionHash, std::vector& outsGlobalIndices, const Callback& callback) +{ + std::thread task(&INodeTrivialRefreshStub::doGetTransactionOutsGlobalIndices, this, transactionHash, std::ref(outsGlobalIndices), callback); + task.detach(); +} + +void INodeTrivialRefreshStub::doGetTransactionOutsGlobalIndices(const crypto::hash& transactionHash, std::vector& outsGlobalIndices, const Callback& callback) +{ + outsGlobalIndices.resize(20); //random + callback(std::error_code()); +} + +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) +{ + if (m_nextTxError) + { + callback(make_error_code(cryptonote::error::INTERNAL_WALLET_ERROR)); + m_nextTxError = false; + return; + } + m_blockchainGenerator.addTxToBlockchain(transaction); + callback(std::error_code()); +} + +void INodeTrivialRefreshStub::getRandomOutsByAmounts(std::vector&& amounts, uint64_t outsCount, std::vector& result, const Callback& callback) +{ + std::thread task(&INodeTrivialRefreshStub::doGetRandomOutsByAmounts, this, amounts, outsCount, std::ref(result), callback); + task.detach(); +} + +void INodeTrivialRefreshStub::doGetRandomOutsByAmounts(std::vector amounts, uint64_t outsCount, std::vector& result, const Callback& callback) +{ + for (uint64_t amount: amounts) + { + cryptonote::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount out; + out.amount = amount; + + for (uint64_t i = 0; i < outsCount; ++i) + { + crypto::public_key key; + crypto::secret_key sk; + generate_keys(key, sk); + + cryptonote::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry e; + e.global_amount_index = i; + e.out_key = key; + + out.outs.push_back(e); + } + } + + callback(std::error_code()); +} + +void INodeTrivialRefreshStub::startAlternativeChain(uint64_t height) +{ + std::vector& blockchain = m_blockchainGenerator.getBlockchain(); + + assert(height < blockchain.size()); + assert(height > m_lastHeight); + + auto it = blockchain.begin(); + std::advance(it, height); + + blockchain.erase(it, blockchain.end()); + + m_lastHeight = height; +} + +void INodeTrivialRefreshStub::setNextTransactionError() +{ + m_nextTxError = true; +} diff --git a/tests/unit_tests/INodeStubs.h b/tests/unit_tests/INodeStubs.h new file mode 100644 index 00000000..28ffa4fb --- /dev/null +++ b/tests/unit_tests/INodeStubs.h @@ -0,0 +1,59 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include + +#include "INode.h" +#include "cryptonote_core/cryptonote_basic.h" +#include "TestBlockchainGenerator.h" + +class INodeDummyStub : public CryptoNote::INode +{ +public: + virtual bool addObserver(CryptoNote::INodeObserver* observer) { return true; }; + virtual bool removeObserver(CryptoNote::INodeObserver* observer) { return true; }; + + virtual void init(const CryptoNote::INode::Callback& callback) {callback(std::error_code());}; + virtual bool shutdown() { return true; }; + + virtual size_t getPeerCount() const { return 0; }; + virtual uint64_t getLastLocalBlockHeight() const { return 0; }; + virtual uint64_t getLastKnownBlockHeight() const { return 0; }; + + 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 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()); }; +}; + +class INodeTrivialRefreshStub : public INodeDummyStub +{ +public: + INodeTrivialRefreshStub(TestBlockchainGenerator& generator) : m_lastHeight(1), m_blockchainGenerator(generator), m_nextTxError(false) {}; + + virtual uint64_t getLastLocalBlockHeight() const { return m_blockchainGenerator.getBlockchain().size() - 1; }; + virtual uint64_t getLastKnownBlockHeight() const { return m_blockchainGenerator.getBlockchain().size() - 1; }; + + 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 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); + + virtual void startAlternativeChain(uint64_t height); + virtual void setNextTransactionError(); + +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 doGetRandomOutsByAmounts(std::vector amounts, uint64_t outsCount, std::vector& result, const Callback& callback); + + uint64_t m_lastHeight; + TestBlockchainGenerator& m_blockchainGenerator; + bool m_nextTxError; +}; diff --git a/tests/unit_tests/TestBlockchainGenerator.cpp b/tests/unit_tests/TestBlockchainGenerator.cpp new file mode 100644 index 00000000..78f5ccd8 --- /dev/null +++ b/tests/unit_tests/TestBlockchainGenerator.cpp @@ -0,0 +1,113 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include "TestBlockchainGenerator.h" + +#include "../performance_tests/multi_tx_test_base.h" + +class TransactionForAddressCreator : public multi_tx_test_base<5> +{ + typedef multi_tx_test_base<5> base_class; +public: + TransactionForAddressCreator() {} + + bool init() + { + return base_class::init(); + } + + void generate(const cryptonote::account_public_address& address, cryptonote::transaction& tx) + { + cryptonote::tx_destination_entry destination(this->m_source_amount, address); + std::vector destinations; + destinations.push_back(destination); + + cryptonote::construct_tx(this->m_miners[this->real_source_idx].get_keys(), this->m_sources, destinations, std::vector(), tx, 0); + } +}; + + +TestBlockchainGenerator::TestBlockchainGenerator() +{ + miner_acc.generate(); + addGenesisBlock(); +} + +std::vector& TestBlockchainGenerator::getBlockchain() +{ + return m_blockchain; +} + +bool TestBlockchainGenerator::getTransactionByHash(const crypto::hash& hash, cryptonote::transaction& tx) +{ + auto it = m_txs.find(hash); + if (it == m_txs.end()) + return false; + + tx = it->second; + return true; +} + +void TestBlockchainGenerator::addGenesisBlock() +{ + cryptonote::block genesis; + uint64_t timestamp = time(NULL); + + generator.construct_block(genesis, miner_acc, timestamp); + m_blockchain.push_back(genesis); +} + +void TestBlockchainGenerator::generateEmptyBlocks(size_t count) +{ + addGenesisBlock(); + + 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); + m_blockchain.push_back(block); + } +} + +void TestBlockchainGenerator::addTxToBlockchain(const cryptonote::transaction& transaction) +{ + crypto::hash txHash = cryptonote::get_transaction_hash(transaction); + m_txs[txHash] = transaction; + + std::list txs; + txs.push_back(transaction); + + cryptonote::block& prev_block = m_blockchain.back(); + cryptonote::block block; + + generator.construct_block(block, prev_block, miner_acc, txs); + m_blockchain.push_back(block); +} + +bool TestBlockchainGenerator::getBlockRewardForAddress(const cryptonote::account_public_address& address) +{ + TransactionForAddressCreator creator; + if (!creator.init()) + return false; + + cryptonote::transaction tx; + creator.generate(address, tx); + + crypto::hash txHash = cryptonote::get_transaction_hash(tx); + m_txs[txHash] = tx; + + std::list txs; + txs.push_back(tx); + + cryptonote::block& prev_block = m_blockchain.back(); + cryptonote::block block; + + generator.construct_block(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 new file mode 100644 index 00000000..0c41a509 --- /dev/null +++ b/tests/unit_tests/TestBlockchainGenerator.h @@ -0,0 +1,32 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include "../core_tests/chaingen.h" +#include +#include + +#include "cryptonote_core/account.h" +#include "cryptonote_core/cryptonote_basic.h" +#include "crypto/hash.h" + +class TestBlockchainGenerator +{ +public: + TestBlockchainGenerator(); + + 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); + +private: + test_generator generator; + cryptonote::account_base miner_acc; + std::vector m_blockchain; + std::unordered_map m_txs; +}; diff --git a/tests/unit_tests/test_format_utils.cpp b/tests/unit_tests/test_format_utils.cpp index 4dd87759..980582ab 100644 --- a/tests/unit_tests/test_format_utils.cpp +++ b/tests/unit_tests/test_format_utils.cpp @@ -109,7 +109,7 @@ TEST(parse_and_validate_tx_extra, is_valid_tx_extra_parsed) cryptonote::account_base acc; acc.generate(); cryptonote::blobdata b = "dsdsdfsdfsf"; - ASSERT_TRUE(cryptonote::construct_miner_tx(0, 0, 10000000000000, 1000, DEFAULT_FEE, acc.get_keys().m_account_address, tx, b, 1)); + ASSERT_TRUE(cryptonote::construct_miner_tx(0, 0, 10000000000000, 1000, MINIMUM_FEE, 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); } @@ -119,7 +119,7 @@ TEST(parse_and_validate_tx_extra, fails_on_big_extra_nonce) 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, DEFAULT_FEE, acc.get_keys().m_account_address, tx, b, 1)); + ASSERT_FALSE(cryptonote::construct_miner_tx(0, 0, 10000000000000, 1000, MINIMUM_FEE, acc.get_keys().m_account_address, tx, b, 1)); } TEST(parse_and_validate_tx_extra, fails_on_wrong_size_in_extra_nonce) { diff --git a/tests/unit_tests/test_wallet.cpp b/tests/unit_tests/test_wallet.cpp new file mode 100644 index 00000000..8bbd838b --- /dev/null +++ b/tests/unit_tests/test_wallet.cpp @@ -0,0 +1,744 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "gtest/gtest.h" + +#include +#include +#include + +#include "INode.h" +#include "wallet/Wallet.h" +#include "cryptonote_core/account.h" + +#include "INodeStubs.h" +#include "TestBlockchainGenerator.h" + +class TrivialWalletObserver : public CryptoNote::IWalletObserver +{ +public: + TrivialWalletObserver() {} + + bool waitForSyncEnd() { + auto future = syncPromise.get_future(); + return future.wait_for(std::chrono::seconds(3)) == std::future_status::ready; + } + + bool waitForSendEnd(std::error_code& ec) { + auto future = sendPromise.get_future(); + + if (future.wait_for(std::chrono::seconds(5)) == std::future_status::timeout) { + return false; + } + + ec = future.get(); + return true; + + } + + bool waitForSaveEnd(std::error_code& ec) { + auto future = savePromise.get_future(); + + if (future.wait_for(std::chrono::seconds(5)) == std::future_status::timeout) { + return false; + } + + ec = future.get(); + return true; + } + + bool waitForLoadEnd(std::error_code& ec) { + auto future = loadPromise.get_future(); + + if (future.wait_for(std::chrono::seconds(5)) == std::future_status::timeout) { + return false; + } + + ec = future.get(); + return true; + } + + void reset() { + syncPromise = std::promise(); + sendPromise = std::promise(); + savePromise = std::promise(); + loadPromise = std::promise(); + } + + virtual void synchronizationProgressUpdated(uint64_t current, uint64_t total, std::error_code result) { + if (result) { + syncPromise.set_value(); + return; + } + + if (current == total) { + syncPromise.set_value(); + } + } + + virtual void sendTransactionCompleted(CryptoNote::TransactionId transactionId, std::error_code result) { + sendPromise.set_value(result); + } + + virtual void saveCompleted(std::error_code result) { + savePromise.set_value(result); + } + + virtual void initCompleted(std::error_code result) { + loadPromise.set_value(result); + } + + virtual void actualBalanceUpdated(uint64_t actualBalance) { +// std::cout << "actual balance: " << actualBalance << std::endl; + } + virtual void pendingBalanceUpdated(uint64_t pendingBalance) { +// std::cout << "pending balance: " << pendingBalance << std::endl; + } + + std::promise syncPromise; + std::promise sendPromise; + std::promise savePromise; + std::promise loadPromise; +}; + +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 = "") { + CryptoNote::Transfer transfer; + transfer.amount = amount; + transfer.address = to.getAddress(); + + return from.sendTransaction(transfer, fee, extra, mixIn); +} + +void WaitWalletSync(TrivialWalletObserver* observer) { + observer->reset(); + ASSERT_TRUE(observer->waitForSyncEnd()); +} + +void WaitWalletSend(TrivialWalletObserver* observer) { + std::error_code ec; + observer->reset(); + ASSERT_TRUE(observer->waitForSendEnd(ec)); +} + +void WaitWalletSend(TrivialWalletObserver* observer, std::error_code& ec) { + observer->reset(); + ASSERT_TRUE(observer->waitForSendEnd(ec)); +} + +void WaitWalletSave(TrivialWalletObserver* observer) { + observer->reset(); + std::error_code ec; + + ASSERT_TRUE(observer->waitForSaveEnd(ec)); + EXPECT_FALSE(ec); +} + +class WalletApi : public ::testing::Test +{ +public: + void SetUp(); + +protected: + void prepareAliceWallet(); + void prepareBobWallet(); + void prepareCarolWallet(); + + void GetOneBlockReward(CryptoNote::Wallet& wallet); + + 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); + + TestBlockchainGenerator generator; + + std::shared_ptr aliceWalletObserver; + std::shared_ptr aliceNode; + std::shared_ptr alice; + + std::shared_ptr bobWalletObserver; + std::shared_ptr bobNode; + std::shared_ptr bob; + + std::shared_ptr carolWalletObserver; + std::shared_ptr carolNode; + std::shared_ptr carol; +}; + +void WalletApi::SetUp() { + prepareAliceWallet(); + generator.generateEmptyBlocks(3); +} + +void WalletApi::prepareAliceWallet() { + aliceNode.reset(new INodeTrivialRefreshStub(generator)); + aliceWalletObserver.reset(new TrivialWalletObserver()); + + alice.reset(new CryptoNote::Wallet(*aliceNode)); + alice->addObserver(aliceWalletObserver.get()); +} + +void WalletApi::prepareBobWallet() { + bobNode.reset(new INodeTrivialRefreshStub(generator)); + bobWalletObserver.reset(new TrivialWalletObserver()); + + bob.reset(new CryptoNote::Wallet(*bobNode)); + bob->addObserver(bobWalletObserver.get()); +} + +void WalletApi::prepareCarolWallet() { + carolNode.reset(new INodeTrivialRefreshStub(generator)); + carolWalletObserver.reset(new TrivialWalletObserver()); + + carol.reset(new CryptoNote::Wallet(*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())); + generator.getBlockRewardForAddress(address); +} + +void WalletApi::performTransferWithErrorTx(const std::array& amounts, uint64_t fee) { + std::vector trs; + CryptoNote::Transfer tr; + tr.address = bob->getAddress(); + tr.amount = amounts[0]; + trs.push_back(tr); + + tr.address = bob->getAddress(); + tr.amount = amounts[1]; + trs.push_back(tr); + + tr.address = carol->getAddress(); + tr.amount = amounts[2]; + trs.push_back(tr); + + aliceNode->setNextTransactionError(); + alice->sendTransaction(trs, fee); + + std::error_code result; + ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get(), result)); + ASSERT_NE(result.value(), 0); + + trs.clear(); + + tr.address = bob->getAddress(); + tr.amount = amounts[3]; + trs.push_back(tr); + + tr.address = carol->getAddress(); + tr.amount = amounts[4]; + trs.push_back(tr); + + alice->sendTransaction(trs, fee); + ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get(), result)); + ASSERT_EQ(result.value(), 0); +} + +void WalletApi::TestSendMoney(int64_t transferAmount, uint64_t fee, uint64_t mixIn, const std::string& extra) { + prepareBobWallet(); + prepareCarolWallet(); + + alice->initAndGenerate("pass"); + + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + ASSERT_NO_FATAL_FAILURE(GetOneBlockReward(*alice)); + + //unblock Alice's money + generator.generateEmptyBlocks(10); + uint64_t expectedBalance = TEST_BLOCK_REWARD; + + alice->startRefresh(); + + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + EXPECT_EQ(alice->pendingBalance(), expectedBalance); + EXPECT_EQ(alice->actualBalance(), expectedBalance); + + bob->initAndGenerate("pass2"); + + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get())); + + ASSERT_NO_FATAL_FAILURE(TransferMoney(*alice, *bob, transferAmount, fee, 0, "")); + + generator.generateEmptyBlocks(10); + + alice->startRefresh(); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + bob->startRefresh(); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get())); + + EXPECT_EQ(bob->pendingBalance(), transferAmount); + EXPECT_EQ(bob->actualBalance(), transferAmount); + + EXPECT_EQ(alice->pendingBalance(), expectedBalance - transferAmount - fee); + EXPECT_EQ(alice->actualBalance(), expectedBalance - transferAmount - fee); + + alice->shutdown(); + bob->shutdown(); +} + +void WaitWalletLoad(TrivialWalletObserver* observer, std::error_code& ec) { + observer->reset(); + + ASSERT_TRUE(observer->waitForLoadEnd(ec)); +} + +TEST_F(WalletApi, refreshWithMoney) { + alice->initAndGenerate("pass"); + + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + 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())); + generator.getBlockRewardForAddress(address); + + alice->startRefresh(); + + 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(); + + alice->initAndGenerate("pass"); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + EXPECT_EQ(alice->getTransactionCount(), 0); + EXPECT_EQ(alice->getTransferCount(), 0); + + ASSERT_NO_FATAL_FAILURE(GetOneBlockReward(*alice)); + + //unblock Alice's money + generator.generateEmptyBlocks(10); + alice->startRefresh(); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + bob->initAndGenerate("pass2"); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get())); + + uint64_t fee = 100000; + int64_t amount1 = 1230000; + ASSERT_NO_FATAL_FAILURE(TransferMoney(*alice, *bob, amount1, fee, 0)); + ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get())); + + int64_t amount2 = 1234500; + ASSERT_NO_FATAL_FAILURE(TransferMoney(*alice, *bob, amount2, fee, 0)); + ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get())); + + int64_t amount3 = 1234567; + ASSERT_NO_FATAL_FAILURE(TransferMoney(*alice, *bob, amount3, fee, 0)); + ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get())); + + carol->initAndGenerate("pass3"); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(carolWalletObserver.get())); + + int64_t amount4 = 1020304; + ASSERT_NO_FATAL_FAILURE(TransferMoney(*alice, *carol, amount4, fee, 0)); + ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get())); + + EXPECT_EQ(alice->getTransactionCount(), 5); + + CryptoNote::Transaction tx; + + //Transaction with id = 0 is tested in getTransactionSuccess + ASSERT_TRUE(alice->getTransaction(1, tx)); + EXPECT_EQ(tx.totalAmount, amount1 + fee); + EXPECT_EQ(tx.fee, fee); + EXPECT_EQ(tx.isCoinbase, false); + EXPECT_EQ(tx.firstTransferId, 0); + EXPECT_EQ(tx.transferCount, 1); + + ASSERT_TRUE(alice->getTransaction(2, tx)); + EXPECT_EQ(tx.totalAmount, amount2 + fee); + EXPECT_EQ(tx.fee, fee); + EXPECT_EQ(tx.isCoinbase, false); + EXPECT_EQ(tx.firstTransferId, 1); + EXPECT_EQ(tx.transferCount, 1); + + ASSERT_TRUE(alice->getTransaction(3, tx)); + EXPECT_EQ(tx.totalAmount, amount3 + fee); + EXPECT_EQ(tx.fee, fee); + EXPECT_EQ(tx.isCoinbase, false); + EXPECT_EQ(tx.firstTransferId, 2); + EXPECT_EQ(tx.transferCount, 1); + + ASSERT_TRUE(alice->getTransaction(4, tx)); + EXPECT_EQ(tx.totalAmount, amount4 + fee); + EXPECT_EQ(tx.fee, fee); + EXPECT_EQ(tx.isCoinbase, false); + EXPECT_EQ(tx.firstTransferId, 3); + EXPECT_EQ(tx.transferCount, 1); + + //Now checking transfers + CryptoNote::Transfer tr; + ASSERT_TRUE(alice->getTransfer(0, tr)); + EXPECT_EQ(tr.amount, amount1); + EXPECT_EQ(tr.address, bob->getAddress()); + + ASSERT_TRUE(alice->getTransfer(1, tr)); + EXPECT_EQ(tr.amount, amount2); + EXPECT_EQ(tr.address, bob->getAddress()); + + ASSERT_TRUE(alice->getTransfer(2, tr)); + EXPECT_EQ(tr.amount, amount3); + EXPECT_EQ(tr.address, bob->getAddress()); + + ASSERT_TRUE(alice->getTransfer(3, tr)); + EXPECT_EQ(tr.amount, amount4); + EXPECT_EQ(tr.address, carol->getAddress()); + + EXPECT_EQ(alice->findTransactionByTransferId(0), 1); + EXPECT_EQ(alice->findTransactionByTransferId(1), 2); + EXPECT_EQ(alice->findTransactionByTransferId(2), 3); + EXPECT_EQ(alice->findTransactionByTransferId(3), 4); + + alice->shutdown(); +} + +TEST_F(WalletApi, saveAndLoadCacheDetails) { + prepareBobWallet(); + prepareCarolWallet(); + + alice->initAndGenerate("pass"); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + ASSERT_NO_FATAL_FAILURE(GetOneBlockReward(*alice)); + + //unblock Alice's money + generator.generateEmptyBlocks(10); + alice->startRefresh(); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + bob->initAndGenerate("pass2"); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get())); + + carol->initAndGenerate("pass3"); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(carolWalletObserver.get())); + + uint64_t fee = 1000000; + int64_t amount1 = 1234567; + int64_t amount2 = 1020304; + int64_t amount3 = 2030405; + + std::vector trs; + CryptoNote::Transfer tr; + tr.address = bob->getAddress(); + tr.amount = amount1; + trs.push_back(tr); + + tr.address = bob->getAddress(); + tr.amount = amount2; + trs.push_back(tr); + + alice->sendTransaction(trs, fee, "", 0, 0); + ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get())); + + trs.clear(); + tr.address = carol->getAddress(); + tr.amount = amount3; + trs.push_back(tr); + + alice->sendTransaction(trs, fee, "", 0, 0); + ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get())); + + std::stringstream archive; + alice->save(archive, true, true); + ASSERT_NO_FATAL_FAILURE(WaitWalletSave(aliceWalletObserver.get())); + + alice->shutdown(); + + prepareAliceWallet(); + + alice->initAndLoad(archive, "pass"); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + ASSERT_EQ(alice->getTransactionCount(), 3); + ASSERT_EQ(alice->getTransferCount(), 3); + + CryptoNote::Transaction tx; + ASSERT_TRUE(alice->getTransaction(1, tx)); + EXPECT_EQ(tx.totalAmount, amount1 + amount2 + fee); + EXPECT_EQ(tx.fee, fee); + EXPECT_EQ(tx.firstTransferId, 0); + EXPECT_EQ(tx.transferCount, 2); + + ASSERT_TRUE(alice->getTransaction(2, tx)); + EXPECT_EQ(tx.totalAmount, amount3 + fee); + EXPECT_EQ(tx.fee, fee); + EXPECT_EQ(tx.firstTransferId, 2); + EXPECT_EQ(tx.transferCount, 1); + + ASSERT_TRUE(alice->getTransfer(0, tr)); + EXPECT_EQ(tr.address, bob->getAddress()); + EXPECT_EQ(tr.amount, amount1); + + ASSERT_TRUE(alice->getTransfer(1, tr)); + EXPECT_EQ(tr.address, bob->getAddress()); + EXPECT_EQ(tr.amount, amount2); + + ASSERT_TRUE(alice->getTransfer(2, tr)); + EXPECT_EQ(tr.address, carol->getAddress()); + EXPECT_EQ(tr.amount, amount3); + + EXPECT_EQ(alice->findTransactionByTransferId(0), 1); + EXPECT_EQ(alice->findTransactionByTransferId(1), 1); + EXPECT_EQ(alice->findTransactionByTransferId(2), 2); + + alice->shutdown(); + carol->shutdown(); + bob->shutdown(); +} + +TEST_F(WalletApi, sendMoneySuccessNoMixin) { + ASSERT_NO_FATAL_FAILURE(TestSendMoney(10000000, 1000000, 0)); +} + +TEST_F(WalletApi, sendMoneySuccessWithMixin) { + ASSERT_NO_FATAL_FAILURE(TestSendMoney(10000000, 1000000, 3)); +} + +TEST_F(WalletApi, getTransactionSuccess) { + alice->initAndGenerate("pass"); + + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + ASSERT_NO_FATAL_FAILURE(GetOneBlockReward(*alice)); + + alice->startRefresh(); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + CryptoNote::Transaction tx; + + ASSERT_EQ(alice->getTransactionCount(), 1); + ASSERT_TRUE(alice->getTransaction(0, tx)); + + EXPECT_EQ(tx.firstTransferId, CryptoNote::INVALID_TRANSFER_ID); + EXPECT_EQ(tx.transferCount, 0); + EXPECT_EQ(tx.totalAmount, TEST_BLOCK_REWARD); + EXPECT_EQ(tx.fee, 0); + EXPECT_EQ(tx.isCoinbase, false); + + alice->shutdown(); +} + +TEST_F(WalletApi, getTransactionFailure) { + alice->initAndGenerate("pass"); + + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + CryptoNote::Transaction tx; + + ASSERT_EQ(alice->getTransactionCount(), 0); + ASSERT_FALSE(alice->getTransaction(0, tx)); + + alice->shutdown(); +} + +TEST_F(WalletApi, useNotInitializedObject) { + EXPECT_THROW(alice->pendingBalance(), std::system_error); + EXPECT_THROW(alice->actualBalance(), std::system_error); + EXPECT_THROW(alice->getTransactionCount(), std::system_error); + EXPECT_THROW(alice->getTransferCount(), std::system_error); + EXPECT_THROW(alice->getAddress(), std::system_error); + + std::stringstream archive; + EXPECT_THROW(alice->save(archive, true, true), std::system_error); + + EXPECT_THROW(alice->findTransactionByTransferId(1), std::system_error); + + CryptoNote::Transaction tx; + CryptoNote::Transfer tr; + EXPECT_THROW(alice->getTransaction(1, tx), std::system_error); + EXPECT_THROW(alice->getTransfer(2, tr), std::system_error); + + tr.address = "lslslslslslsls"; + tr.amount = 1000000; + EXPECT_THROW(alice->sendTransaction(tr, 300201), std::system_error); + + std::vector trs; + trs.push_back(tr); + EXPECT_THROW(alice->sendTransaction(trs, 329293), std::system_error); +} + +TEST_F(WalletApi, sendWrongAmount) { + alice->initAndGenerate("pass"); + + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + CryptoNote::Transfer tr; + tr.address = "1234567890qwertasdfgzxcvbyuiophjklnm"; + tr.amount = 1; + + EXPECT_THROW(alice->sendTransaction(tr, 1), std::system_error); + + alice->shutdown(); +} + +TEST_F(WalletApi, wrongPassword) { + alice->initAndGenerate("pass"); + + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + std::stringstream archive; + alice->save(archive, true, false); + ASSERT_NO_FATAL_FAILURE(WaitWalletSave(aliceWalletObserver.get())); + + alice->shutdown(); + + prepareAliceWallet(); + alice->initAndLoad(archive, "wrongpass"); + + std::error_code result; + ASSERT_NO_FATAL_FAILURE(WaitWalletLoad(aliceWalletObserver.get(), result)); + EXPECT_EQ(result.value(), cryptonote::error::WRONG_PASSWORD); +} + +TEST_F(WalletApi, detachBlockchain) { + alice->initAndGenerate("pass"); + + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + ASSERT_NO_FATAL_FAILURE(GetOneBlockReward(*alice)); + + generator.generateEmptyBlocks(10); + alice->startRefresh(); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + aliceNode->startAlternativeChain(3); + generator.generateEmptyBlocks(10); + alice->startRefresh(); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + EXPECT_EQ(alice->actualBalance(), 0); + EXPECT_EQ(alice->pendingBalance(), 0); + + alice->shutdown(); +} + +TEST_F(WalletApi, saveAndLoadErroneousTxsCacheDetails) { + prepareBobWallet(); + prepareCarolWallet(); + + alice->initAndGenerate("pass"); + + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + ASSERT_NO_FATAL_FAILURE(GetOneBlockReward(*alice)); + generator.generateEmptyBlocks(10); + alice->startRefresh(); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + bob->initAndGenerate("pass"); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get())); + + carol->initAndGenerate("pass"); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(carolWalletObserver.get())); + + std::array amounts; + amounts[0] = 1234567; + amounts[1] = 1345678; + amounts[2] = 1456789; + amounts[3] = 1567890; + amounts[4] = 1678901; + uint64_t fee = 10000; + + ASSERT_NO_FATAL_FAILURE(performTransferWithErrorTx(amounts, fee)); + + std::stringstream archive; + alice->save(archive); + + ASSERT_NO_FATAL_FAILURE(WaitWalletSave(aliceWalletObserver.get())); + + prepareAliceWallet(); + alice->initAndLoad(archive, "pass"); + + std::error_code result; + ASSERT_NO_FATAL_FAILURE(WaitWalletLoad(aliceWalletObserver.get(), result)); + ASSERT_EQ(result.value(), 0); + + EXPECT_EQ(alice->getTransactionCount(), 2); + EXPECT_EQ(alice->getTransferCount(), 2); + + CryptoNote::Transaction tx; + ASSERT_TRUE(alice->getTransaction(1, tx)); + EXPECT_EQ(tx.totalAmount, amounts[3] + amounts[4] + fee); + EXPECT_EQ(tx.firstTransferId, 0); + EXPECT_EQ(tx.transferCount, 2); + + CryptoNote::Transfer tr; + ASSERT_TRUE(alice->getTransfer(0, tr)); + EXPECT_EQ(tr.amount, amounts[3]); + EXPECT_EQ(tr.address, bob->getAddress()); + + ASSERT_TRUE(alice->getTransfer(1, tr)); + EXPECT_EQ(tr.amount, amounts[4]); + EXPECT_EQ(tr.address, carol->getAddress()); + + alice->shutdown(); +} + +TEST_F(WalletApi, saveAndLoadErroneousTxsCacheNoDetails) { + prepareBobWallet(); + prepareCarolWallet(); + + alice->initAndGenerate("pass"); + + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + ASSERT_NO_FATAL_FAILURE(GetOneBlockReward(*alice)); + generator.generateEmptyBlocks(10); + alice->startRefresh(); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get())); + + bob->initAndGenerate("pass"); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get())); + + carol->initAndGenerate("pass"); + ASSERT_NO_FATAL_FAILURE(WaitWalletSync(carolWalletObserver.get())); + + std::array amounts; + amounts[0] = 1234567; + amounts[1] = 1345678; + amounts[2] = 1456789; + amounts[3] = 1567890; + amounts[4] = 1678901; + uint64_t fee = 10000; + + ASSERT_NO_FATAL_FAILURE(performTransferWithErrorTx(amounts, fee)); + + std::stringstream archive; + alice->save(archive, false, true); + + ASSERT_NO_FATAL_FAILURE(WaitWalletSave(aliceWalletObserver.get())); + + prepareAliceWallet(); + alice->initAndLoad(archive, "pass"); + + std::error_code result; + ASSERT_NO_FATAL_FAILURE(WaitWalletLoad(aliceWalletObserver.get(), result)); + ASSERT_EQ(result.value(), 0); + + EXPECT_EQ(alice->getTransactionCount(), 2); + EXPECT_EQ(alice->getTransferCount(), 0); + + CryptoNote::Transaction tx; + ASSERT_TRUE(alice->getTransaction(1, tx)); + EXPECT_EQ(tx.totalAmount, amounts[3] + amounts[4] + fee); + EXPECT_EQ(tx.firstTransferId, CryptoNote::INVALID_TRANSFER_ID); + EXPECT_EQ(tx.transferCount, 0); + + alice->shutdown(); +}