diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt index eaa97d2d..688e8a1e 100644 --- a/ReleaseNotes.txt +++ b/ReleaseNotes.txt @@ -1,3 +1,8 @@ +Release notes 1.0.9 + +- New API for Bytecoin RPC Wallet +- Various improvements + Release notes 1.0.8 - Fusion transactions for Bytecoin Wallet diff --git a/include/ITransfersContainer.h b/include/ITransfersContainer.h index 8aca7ee8..ae7d720a 100644 --- a/include/ITransfersContainer.h +++ b/include/ITransfersContainer.h @@ -97,7 +97,8 @@ public: virtual size_t transactionsCount() const = 0; virtual uint64_t balance(uint32_t flags = IncludeDefault) const = 0; virtual void getOutputs(std::vector& transfers, uint32_t flags = IncludeDefault) const = 0; - virtual bool getTransactionInformation(const Crypto::Hash& transactionHash, TransactionInformation& info, int64_t& txBalance) const = 0; + virtual bool getTransactionInformation(const Crypto::Hash& transactionHash, TransactionInformation& info, + uint64_t* amountIn = nullptr, uint64_t* amountOut = nullptr) const = 0; virtual std::vector getTransactionOutputs(const Crypto::Hash& transactionHash, uint32_t flags = IncludeDefault) const = 0; //only type flags are feasible for this function virtual std::vector getTransactionInputs(const Crypto::Hash& transactionHash, uint32_t flags) const = 0; diff --git a/include/ITransfersSynchronizer.h b/include/ITransfersSynchronizer.h index d49da55e..abc9ce98 100644 --- a/include/ITransfersSynchronizer.h +++ b/include/ITransfersSynchronizer.h @@ -62,6 +62,16 @@ public: virtual ITransfersContainer& getContainer() = 0; }; +class ITransfersSynchronizerObserver { +public: + virtual void onBlocksAdded(const Crypto::PublicKey& viewPublicKey, const std::vector& blockHashes) {} + virtual void onBlockchainDetach(const Crypto::PublicKey& viewPublicKey, uint32_t blockIndex) {} + virtual void onTransactionDeleteBegin(const Crypto::PublicKey& viewPublicKey, Crypto::Hash transactionHash) {} + virtual void onTransactionDeleteEnd(const Crypto::PublicKey& viewPublicKey, Crypto::Hash transactionHash) {} + virtual void onTransactionUpdated(const Crypto::PublicKey& viewPublicKey, const Crypto::Hash& transactionHash, + const std::vector& containers) {} +}; + class ITransfersSynchronizer : public IStreamSerializable { public: virtual ~ITransfersSynchronizer() {} @@ -71,6 +81,7 @@ public: virtual void getSubscriptions(std::vector& subscriptions) = 0; // returns nullptr if address is not found virtual ITransfersSubscription* getSubscription(const AccountPublicAddress& acc) = 0; + virtual std::vector getViewKeyKnownBlocks(const Crypto::PublicKey& publicViewKey) = 0; }; } diff --git a/include/IWallet.h b/include/IWallet.h index 49f1b3b9..c48b2f98 100755 --- a/include/IWallet.h +++ b/include/IWallet.h @@ -32,7 +32,8 @@ enum class WalletTransactionState : uint8_t { SUCCEEDED = 0, FAILED, CANCELLED, - CREATED + CREATED, + DELETED }; enum WalletEventType { @@ -40,7 +41,7 @@ enum WalletEventType { TRANSACTION_UPDATED, BALANCE_UNLOCKED, SYNC_PROGRESS_UPDATED, - SYNC_COMPLETED + SYNC_COMPLETED, }; struct WalletTransactionCreatedData { @@ -80,7 +81,8 @@ struct WalletTransaction { enum class WalletTransferType : uint8_t { USUAL = 0, - DONATION + DONATION, + CHANGE }; struct WalletOrder { @@ -100,13 +102,24 @@ struct DonationSettings { }; struct TransactionParameters { - std::string sourceAddress; + std::vector sourceAddresses; std::vector destinations; uint64_t fee = 0; uint64_t mixIn = 0; std::string extra; uint64_t unlockTimestamp = 0; DonationSettings donation; + std::string changeDestination; +}; + +struct WalletTransactionWithTransfers { + WalletTransaction transaction; + std::vector transfers; +}; + +struct TransactionsInBlockInfo { + Crypto::Hash blockHash; + std::vector transactions; }; class IWallet { @@ -124,6 +137,7 @@ public: virtual size_t getAddressCount() const = 0; virtual std::string getAddress(size_t index) const = 0; virtual KeyPair getAddressSpendKey(size_t index) const = 0; + virtual KeyPair getAddressSpendKey(const std::string& address) const = 0; virtual KeyPair getViewKey() const = 0; virtual std::string createAddress() = 0; virtual std::string createAddress(const Crypto::SecretKey& spendSecretKey) = 0; @@ -140,12 +154,20 @@ public: virtual size_t getTransactionTransferCount(size_t transactionIndex) const = 0; virtual WalletTransfer getTransactionTransfer(size_t transactionIndex, size_t transferIndex) const = 0; - virtual size_t transfer(const WalletOrder& destination, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0) = 0; - virtual size_t transfer(const std::vector& destinations, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0) = 0; - virtual size_t transfer(const std::string& sourceAddress, const WalletOrder& destination, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0) = 0; - virtual size_t transfer(const std::string& sourceAddress, const std::vector& destinations, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0) = 0; + virtual WalletTransactionWithTransfers getTransaction(const Crypto::Hash& transactionHash) const = 0; + virtual std::vector getTransactions(const Crypto::Hash& blockHash, size_t count) const = 0; + virtual std::vector getTransactions(uint32_t blockIndex, size_t count) const = 0; + virtual std::vector getBlockHashes(uint32_t blockIndex, size_t count) const = 0; + virtual uint32_t getBlockCount() const = 0; + virtual std::vector getUnconfirmedTransactions() const = 0; + virtual std::vector getDelayedTransactionIds() const = 0; + virtual size_t transfer(const TransactionParameters& sendingTransaction) = 0; + virtual size_t makeTransaction(const TransactionParameters& sendingTransaction) = 0; + virtual void commitTransaction(size_t transactionId) = 0; + virtual void rollbackUncommitedTransaction(size_t transactionId) = 0; + virtual void start() = 0; virtual void stop() = 0; diff --git a/src/BlockchainExplorer/BlockchainExplorer.cpp b/src/BlockchainExplorer/BlockchainExplorer.cpp index 71f4e42e..2a522aba 100755 --- a/src/BlockchainExplorer/BlockchainExplorer.cpp +++ b/src/BlockchainExplorer/BlockchainExplorer.cpp @@ -87,12 +87,16 @@ bool BlockchainExplorer::PoolUpdateGuard::beginUpdate() { for (;;) { switch (state) { case State::NONE: - return true; + if (m_state.compare_exchange_weak(state, State::UPDATING)) { + return true; + } + break; case State::UPDATING: if (m_state.compare_exchange_weak(state, State::UPDATE_REQUIRED)) { return false; } + break; case State::UPDATE_REQUIRED: return false; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 16e89dfe..f6ea4393 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -30,6 +30,7 @@ file(GLOB_RECURSE JsonRpcServer JsonRpcServer/*) file(GLOB_RECURSE PaymentGate PaymentGate/*) file(GLOB_RECURSE PaymentGateService PaymentGateService/*) +file(GLOB_RECURSE Miner Miner/*) source_group("" FILES $${Common} ${ConnectivityTool} ${Crypto} ${CryptoNoteCore} ${CryptoNoteProtocol} ${Daemon} ${JsonRpcServer} ${Http} ${Logging} ${NodeRpcProxy} ${P2p} ${Rpc} ${Serialization} ${SimpleWallet} ${System} ${Transfers} ${Wallet} ${WalletLegacy}) @@ -54,17 +55,17 @@ add_executable(ConnectivityTool ${ConnectivityTool}) add_executable(Daemon ${Daemon}) add_executable(SimpleWallet ${SimpleWallet}) add_executable(PaymentGateService ${PaymentGateService}) +add_executable(Miner ${Miner}) +if (MSVC) + target_link_libraries(System ws2_32) +endif () target_link_libraries(ConnectivityTool CryptoNoteCore Common Logging Crypto P2P Rpc Http Serialization System ${Boost_LIBRARIES}) target_link_libraries(Daemon CryptoNoteCore P2P Rpc Serialization System Http Logging Common Crypto upnpc-static BlockchainExplorer ${Boost_LIBRARIES}) target_link_libraries(SimpleWallet Wallet NodeRpcProxy Transfers Rpc Http Serialization CryptoNoteCore System Logging Common Crypto ${Boost_LIBRARIES}) target_link_libraries(PaymentGateService PaymentGate JsonRpcServer Wallet NodeRpcProxy Transfers CryptoNoteCore Crypto P2P Rpc Http Serialization System Logging Common InProcessNode upnpc-static BlockchainExplorer ${Boost_LIBRARIES}) - -if (MSVC) - target_link_libraries(ConnectivityTool ws2_32) - target_link_libraries(SimpleWallet ws2_32) -endif () +target_link_libraries(Miner CryptoNoteCore Rpc Serialization System Http Logging Common Crypto ${Boost_LIBRARIES}) add_dependencies(Rpc version) @@ -78,3 +79,4 @@ set_property(TARGET ConnectivityTool PROPERTY OUTPUT_NAME "connectivity_tool") set_property(TARGET Daemon PROPERTY OUTPUT_NAME "bytecoind") set_property(TARGET SimpleWallet PROPERTY OUTPUT_NAME "simplewallet") set_property(TARGET PaymentGateService PROPERTY OUTPUT_NAME "walletd") +set_property(TARGET Miner PROPERTY OUTPUT_NAME "miner") diff --git a/src/Common/ObserverManager.h b/src/Common/ObserverManager.h index 585041db..45c49505 100755 --- a/src/Common/ObserverManager.h +++ b/src/Common/ObserverManager.h @@ -120,6 +120,32 @@ public: } } + template + void notify(F notification, const Arg0& arg0, const Arg1& arg1, const Arg2& arg2, const Arg3& arg3, const Arg4& arg4) { + std::vector observersCopy; + { + std::unique_lock lock(m_observersMutex); + observersCopy = m_observers; + } + + for (T* observer : observersCopy) { + (observer->*notification)(arg0, arg1, arg2, arg3, arg4); + } + } + + template + void notify(F notification, const Arg0& arg0, const Arg1& arg1, const Arg2& arg2, const Arg3& arg3, const Arg4& arg4, const Arg5& arg5) { + std::vector observersCopy; + { + std::unique_lock lock(m_observersMutex); + observersCopy = m_observers; + } + + for (T* observer : observersCopy) { + (observer->*notification)(arg0, arg1, arg2, arg3, arg4, arg5); + } + } + #else template diff --git a/src/Common/ScopeExit.cpp b/src/Common/ScopeExit.cpp new file mode 100644 index 00000000..8ea6d922 --- /dev/null +++ b/src/Common/ScopeExit.cpp @@ -0,0 +1,37 @@ +// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "ScopeExit.h" + +namespace Tools { + +ScopeExit::ScopeExit(std::function&& handler) : + m_handler(std::move(handler)), + m_cancelled(false) { +} + +ScopeExit::~ScopeExit() { + if (!m_cancelled) { + m_handler(); + } +} + +void ScopeExit::cancel() { + m_cancelled = true; +} + +} diff --git a/src/Common/ScopeExit.h b/src/Common/ScopeExit.h new file mode 100644 index 00000000..76bc66da --- /dev/null +++ b/src/Common/ScopeExit.h @@ -0,0 +1,41 @@ +// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include + +namespace Tools { + +class ScopeExit { +public: + ScopeExit(std::function&& handler); + ~ScopeExit(); + + ScopeExit(const ScopeExit&) = delete; + ScopeExit(ScopeExit&&) = delete; + ScopeExit& operator=(const ScopeExit&) = delete; + ScopeExit& operator=(ScopeExit&&) = delete; + + void cancel(); + +private: + std::function m_handler; + bool m_cancelled; +}; + +} diff --git a/src/Common/Util.cpp b/src/Common/Util.cpp index 8c8dec57..9ef23343 100644 --- a/src/Common/Util.cpp +++ b/src/Common/Util.cpp @@ -359,4 +359,9 @@ std::string get_nix_version_display_string() return std::error_code(code, std::system_category()); } + bool directoryExists(const std::string& path) { + boost::system::error_code ec; + return boost::filesystem::is_directory(path, ec); + } + } diff --git a/src/Common/Util.h b/src/Common/Util.h index bf4eac15..047d96fd 100755 --- a/src/Common/Util.h +++ b/src/Common/Util.h @@ -26,4 +26,5 @@ namespace Tools std::string get_os_version_string(); bool create_directories_if_necessary(const std::string& path); std::error_code replace_file(const std::string& replacement_name, const std::string& replaced_name); + bool directoryExists(const std::string& path); } diff --git a/src/CryptoNoteConfig.h b/src/CryptoNoteConfig.h index 2af37810..ad261adf 100644 --- a/src/CryptoNoteConfig.h +++ b/src/CryptoNoteConfig.h @@ -165,7 +165,8 @@ const CheckpointData CHECKPOINTS[] = { {810500, "302b2349f221232820adc3dadafd8a61b035491e33af669c78a687949eb0a381"}, {816000, "32b7fdd4e4d715db81f8f09f4ba5e5c78e8113f2804d61a57378baee479ce745"}, {822000, "a3c9603c6813a0dc0efc40db288c356d1a7f02d1d2e47bee04346e73715f8984"}, - {841000, "2cffb6504ee38f708a6256a63585f9382b3b426e64b4504236c70678bd160dce"} + {841000, "2cffb6504ee38f708a6256a63585f9382b3b426e64b4504236c70678bd160dce"}, + {890000, "a7132932ea31236ce6b8775cd1380edf90b5e536ee4202c77b69a3d62445fcd2"} }; } // CryptoNote diff --git a/src/CryptoNoteCore/Blockchain.cpp b/src/CryptoNoteCore/Blockchain.cpp index f069131e..0485da74 100644 --- a/src/CryptoNoteCore/Blockchain.cpp +++ b/src/CryptoNoteCore/Blockchain.cpp @@ -319,7 +319,6 @@ m_currency(currency), m_tx_pool(tx_pool), m_current_block_cumul_sz_limit(0), m_is_in_checkpoint_zone(false), -m_is_blockchain_storing(false), m_upgradeDetector(currency, m_blocks, BLOCK_MAJOR_VERSION_2, logger), m_checkpoints(logger) { diff --git a/src/CryptoNoteCore/Blockchain.h b/src/CryptoNoteCore/Blockchain.h index f748fbfc..a0b40bc6 100755 --- a/src/CryptoNoteCore/Blockchain.h +++ b/src/CryptoNoteCore/Blockchain.h @@ -107,7 +107,6 @@ namespace CryptoNote { bool get_out_by_msig_gindex(uint64_t amount, uint64_t gindex, MultisignatureOutput& out); bool checkTransactionInputs(const Transaction& tx, uint32_t& pmax_used_block_height, Crypto::Hash& max_used_block_id, BlockInfo* tail = 0); uint64_t getCurrentCumulativeBlocksizeLimit(); - bool isStoringBlockchain(){return m_is_blockchain_storing;} uint64_t blockDifficulty(size_t i); bool getBlockContainingTransaction(const Crypto::Hash& txId, Crypto::Hash& blockId, uint32_t& blockHeight); bool getAlreadyGeneratedCoins(const Crypto::Hash& hash, uint64_t& generatedCoins); @@ -248,7 +247,6 @@ namespace CryptoNote { std::string m_config_folder; Checkpoints m_checkpoints; std::atomic m_is_in_checkpoint_zone; - std::atomic m_is_blockchain_storing; typedef SwappedVector Blocks; typedef std::unordered_map BlockMap; diff --git a/src/CryptoNoteCore/Core.cpp b/src/CryptoNoteCore/Core.cpp index a8238c5e..d6d52891 100755 --- a/src/CryptoNoteCore/Core.cpp +++ b/src/CryptoNoteCore/Core.cpp @@ -99,11 +99,6 @@ bool core::handle_command_line(const boost::program_options::variables_map& vm) return true; } -bool core::is_ready() { - return !m_blockchain.isStoringBlockchain(); -} - - uint32_t core::get_current_blockchain_height() { return m_blockchain.getCurrentBlockchainHeight(); } diff --git a/src/CryptoNoteCore/Core.h b/src/CryptoNoteCore/Core.h index 2afaa5b2..e2838e86 100755 --- a/src/CryptoNoteCore/Core.h +++ b/src/CryptoNoteCore/Core.h @@ -97,7 +97,6 @@ namespace CryptoNote { std::vector buildSparseChain() override; std::vector buildSparseChain(const Crypto::Hash& startBlockId) override; void on_synchronized() override; - bool is_ready() override; virtual void get_blockchain_top(uint32_t& height, Crypto::Hash& top_id) override; bool get_blocks(uint32_t start_offset, uint32_t count, std::list& blocks, std::list& txs); diff --git a/src/CryptoNoteCore/CoreConfig.cpp b/src/CryptoNoteCore/CoreConfig.cpp index 96b12c63..14c83d03 100755 --- a/src/CryptoNoteCore/CoreConfig.cpp +++ b/src/CryptoNoteCore/CoreConfig.cpp @@ -29,6 +29,7 @@ CoreConfig::CoreConfig() { void CoreConfig::init(const boost::program_options::variables_map& options) { if (options.count(command_line::arg_data_dir.name) != 0 && (!options[command_line::arg_data_dir.name].defaulted() || configFolder == Tools::getDefaultDataDirectory())) { configFolder = command_line::get_arg(options, command_line::arg_data_dir); + configFolderDefaulted = options[command_line::arg_data_dir.name].defaulted(); } } diff --git a/src/CryptoNoteCore/CoreConfig.h b/src/CryptoNoteCore/CoreConfig.h index 2a04edca..7ef7cbc6 100644 --- a/src/CryptoNoteCore/CoreConfig.h +++ b/src/CryptoNoteCore/CoreConfig.h @@ -31,6 +31,7 @@ public: void init(const boost::program_options::variables_map& options); std::string configFolder; + bool configFolderDefaulted = true; }; } //namespace CryptoNote diff --git a/src/CryptoNoteCore/ICore.h b/src/CryptoNoteCore/ICore.h index 72b4460f..e779a167 100755 --- a/src/CryptoNoteCore/ICore.h +++ b/src/CryptoNoteCore/ICore.h @@ -68,7 +68,6 @@ public: virtual bool handle_incoming_block_blob(const CryptoNote::BinaryArray& block_blob, CryptoNote::block_verification_context& bvc, bool control_miner, bool relay_block) = 0; virtual bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS_request& arg, NOTIFY_RESPONSE_GET_OBJECTS_request& rsp) = 0; //Deprecated. Should be removed with CryptoNoteProtocolHandler. virtual void on_synchronized() = 0; - virtual bool is_ready() = 0; virtual size_t addChain(const std::vector& chain) = 0; virtual void get_blockchain_top(uint32_t& height, Crypto::Hash& top_id) = 0; diff --git a/src/CryptoNoteProtocol/CryptoNoteProtocolHandler.cpp b/src/CryptoNoteProtocol/CryptoNoteProtocolHandler.cpp index 554e0420..969c5b95 100644 --- a/src/CryptoNoteProtocol/CryptoNoteProtocolHandler.cpp +++ b/src/CryptoNoteProtocol/CryptoNoteProtocolHandler.cpp @@ -108,6 +108,9 @@ bool CryptoNoteProtocolHandler::start_sync(CryptoNoteConnectionContext& context) logger(Logging::TRACE) << context << "Starting synchronization"; if (context.m_state == CryptoNoteConnectionContext::state_synchronizing) { + assert(context.m_needed_objects.empty()); + assert(context.m_requested_objects.empty()); + NOTIFY_REQUEST_CHAIN::request r = boost::value_initialized(); r.block_ids = m_core.buildSparseChain(); logger(Logging::TRACE) << context << "-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size(); @@ -355,15 +358,16 @@ int CryptoNoteProtocolHandler::handle_response_get_objects(int command, NOTIFY_R } } - auto req_it = context.m_requested_objects.find(get_block_hash(b)); + auto blockHash = get_block_hash(b); + auto req_it = context.m_requested_objects.find(blockHash); if (req_it == context.m_requested_objects.end()) { - logger(Logging::ERROR) << context << "sent wrong NOTIFY_RESPONSE_GET_OBJECTS: block with id=" << Common::podToHex(getBinaryArrayHash(asBinaryArray(block_entry.block))) + logger(Logging::ERROR) << context << "sent wrong NOTIFY_RESPONSE_GET_OBJECTS: block with id=" << Common::podToHex(blockHash) << " wasn't requested, dropping connection"; context.m_state = CryptoNoteConnectionContext::state_shutdown; return 1; } if (b.transactionHashes.size() != block_entry.txs.size()) { - logger(Logging::ERROR) << context << "sent wrong NOTIFY_RESPONSE_GET_OBJECTS: block with id=" << Common::podToHex(getBinaryArrayHash(asBinaryArray(block_entry.block))) + logger(Logging::ERROR) << context << "sent wrong NOTIFY_RESPONSE_GET_OBJECTS: block with id=" << Common::podToHex(blockHash) << ", transactionHashes.size()=" << b.transactionHashes.size() << " mismatch with block_complete_entry.m_txs.size()=" << block_entry.txs.size() << ", dropping connection"; context.m_state = CryptoNoteConnectionContext::state_shutdown; return 1; @@ -437,6 +441,8 @@ int CryptoNoteProtocolHandler::processObjects(CryptoNoteConnectionContext& conte } else if (bvc.m_already_exists) { logger(Logging::DEBUGGING) << context << "Block already exists, switching to idle state"; context.m_state = CryptoNoteConnectionContext::state_idle; + context.m_needed_objects.clear(); + context.m_requested_objects.clear(); return 1; } diff --git a/src/Daemon/Daemon.cpp b/src/Daemon/Daemon.cpp index c96783ca..38d10766 100755 --- a/src/Daemon/Daemon.cpp +++ b/src/Daemon/Daemon.cpp @@ -202,11 +202,21 @@ int main(int argc, char* argv[]) RpcServerConfig rpcConfig; rpcConfig.init(vm); + if (!coreConfig.configFolderDefaulted) { + if (!Tools::directoryExists(coreConfig.configFolder)) { + throw std::runtime_error("Directory does not exist: " + coreConfig.configFolder); + } + } else { + if (!Tools::create_directories_if_necessary(coreConfig.configFolder)) { + throw std::runtime_error("Can't create directory: " + coreConfig.configFolder); + } + } + System::Dispatcher dispatcher; CryptoNote::CryptoNoteProtocolHandler cprotocol(currency, dispatcher, ccore, nullptr, logManager); CryptoNote::NodeServer p2psrv(dispatcher, cprotocol, logManager); - CryptoNote::RpcServer rpcServer(dispatcher, logManager, ccore, p2psrv); + CryptoNote::RpcServer rpcServer(dispatcher, logManager, ccore, p2psrv, cprotocol); cprotocol.set_p2p_endpoint(&p2psrv); ccore.set_cryptonote_protocol(&cprotocol); diff --git a/src/Miner/BlockchainMonitor.cpp b/src/Miner/BlockchainMonitor.cpp new file mode 100644 index 00000000..d466ddf8 --- /dev/null +++ b/src/Miner/BlockchainMonitor.cpp @@ -0,0 +1,105 @@ +// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "BlockchainMonitor.h" + +#include "Common/StringTools.h" + +#include +#include +#include + +#include "Rpc/CoreRpcServerCommandsDefinitions.h" +#include "Rpc/JsonRpc.h" +#include "Rpc/HttpClient.h" + +BlockchainMonitor::BlockchainMonitor(System::Dispatcher& dispatcher, const std::string& daemonHost, uint16_t daemonPort, size_t pollingInterval, Logging::ILogger& logger): + m_dispatcher(dispatcher), + m_daemonHost(daemonHost), + m_daemonPort(daemonPort), + m_pollingInterval(pollingInterval), + m_stopped(false), + m_httpEvent(dispatcher), + m_sleepingContext(dispatcher), + m_logger(logger, "BlockchainMonitor") { + + m_httpEvent.set(); +} + +void BlockchainMonitor::waitBlockchainUpdate() { + m_logger(Logging::DEBUGGING) << "Waiting for blockchain updates"; + m_stopped = false; + + Crypto::Hash lastBlockHash = requestLastBlockHash(); + + while(!m_stopped) { + m_sleepingContext.spawn([this] () { + System::Timer timer(m_dispatcher); + timer.sleep(std::chrono::seconds(m_pollingInterval)); + }); + + m_sleepingContext.wait(); + + if (lastBlockHash != requestLastBlockHash()) { + m_logger(Logging::DEBUGGING) << "Blockchain has been updated"; + break; + } + } + + if (m_stopped) { + m_logger(Logging::DEBUGGING) << "Blockchain monitor has been stopped"; + throw System::InterruptedException(); + } +} + +void BlockchainMonitor::stop() { + m_logger(Logging::DEBUGGING) << "Sending stop signal to blockchain monitor"; + m_stopped = true; + + m_sleepingContext.interrupt(); + m_sleepingContext.wait(); +} + +Crypto::Hash BlockchainMonitor::requestLastBlockHash() { + m_logger(Logging::DEBUGGING) << "Requesting last block hash"; + + try { + CryptoNote::HttpClient client(m_dispatcher, m_daemonHost, m_daemonPort); + + CryptoNote::COMMAND_RPC_GET_LAST_BLOCK_HEADER::request request; + CryptoNote::COMMAND_RPC_GET_LAST_BLOCK_HEADER::response response; + + System::EventLock lk(m_httpEvent); + CryptoNote::JsonRpc::invokeJsonRpcCommand(client, "getlastblockheader", request, response); + + if (response.status != CORE_RPC_STATUS_OK) { + throw std::runtime_error("Core responded with wrong status: " + response.status); + } + + Crypto::Hash blockHash; + if (!Common::podFromHex(response.block_header.hash, blockHash)) { + throw std::runtime_error("Couldn't parse block hash: " + response.block_header.hash); + } + + m_logger(Logging::DEBUGGING) << "Last block hash: " << Common::podToHex(blockHash); + + return blockHash; + } catch (std::exception& e) { + m_logger(Logging::ERROR) << "Failed to request last block hash: " << e.what(); + throw; + } +} diff --git a/src/Miner/BlockchainMonitor.h b/src/Miner/BlockchainMonitor.h new file mode 100644 index 00000000..c03fbc70 --- /dev/null +++ b/src/Miner/BlockchainMonitor.h @@ -0,0 +1,46 @@ +// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include "CryptoTypes.h" + +#include +#include +#include + +#include "Logging/LoggerRef.h" + +class BlockchainMonitor { +public: + BlockchainMonitor(System::Dispatcher& dispatcher, const std::string& daemonHost, uint16_t daemonPort, size_t pollingInterval, Logging::ILogger& logger); + + void waitBlockchainUpdate(); + void stop(); +private: + System::Dispatcher& m_dispatcher; + std::string m_daemonHost; + uint16_t m_daemonPort; + size_t m_pollingInterval; + bool m_stopped; + System::Event m_httpEvent; + System::ContextGroup m_sleepingContext; + + Logging::LoggerRef m_logger; + + Crypto::Hash requestLastBlockHash(); +}; diff --git a/src/Miner/Miner.cpp b/src/Miner/Miner.cpp new file mode 100644 index 00000000..5d048d8d --- /dev/null +++ b/src/Miner/Miner.cpp @@ -0,0 +1,157 @@ +// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "Miner.h" + +#include + +#include "crypto/crypto.h" +#include "CryptoNoteCore/CryptoNoteFormatUtils.h" + +#include + +namespace CryptoNote { + +Miner::Miner(System::Dispatcher& dispatcher, Logging::ILogger& logger) : + m_dispatcher(dispatcher), + m_miningStopped(dispatcher), + m_state(MiningState::MINING_STOPPED), + m_logger(logger, "Miner") { +} + +Miner::~Miner() { + assert(m_state != MiningState::MINING_IN_PROGRESS); +} + +Block Miner::mine(const BlockMiningParameters& blockMiningParameters, size_t threadCount) { + if (threadCount == 0) { + throw std::runtime_error("Miner requires at least one thread"); + } + + if (m_state == MiningState::MINING_IN_PROGRESS) { + throw std::runtime_error("Mining is already in progress"); + } + + m_state = MiningState::MINING_IN_PROGRESS; + m_miningStopped.clear(); + + runWorkers(blockMiningParameters, threadCount); + + assert(m_state != MiningState::MINING_IN_PROGRESS); + if (m_state == MiningState::MINING_STOPPED) { + m_logger(Logging::DEBUGGING) << "Mining has been stopped"; + throw System::InterruptedException(); + } + + assert(m_state == MiningState::BLOCK_FOUND); + return m_block; +} + +void Miner::stop() { + MiningState state = MiningState::MINING_IN_PROGRESS; + + if (m_state.compare_exchange_weak(state, MiningState::MINING_STOPPED)) { + m_miningStopped.wait(); + m_miningStopped.clear(); + } +} + +void Miner::runWorkers(BlockMiningParameters blockMiningParameters, size_t threadCount) { + assert(threadCount > 0); + + m_logger(Logging::INFO) << "Starting mining for difficulty " << blockMiningParameters.difficulty; + + try { + blockMiningParameters.blockTemplate.nonce = Crypto::rand(); + + for (size_t i = 0; i < threadCount; ++i) { + m_workers.emplace_back(std::unique_ptr> ( + new System::RemoteContext(m_dispatcher, std::bind(&Miner::workerFunc, this, blockMiningParameters.blockTemplate, blockMiningParameters.difficulty, threadCount))) + ); + + blockMiningParameters.blockTemplate.nonce++; + } + + m_workers.clear(); + + } catch (std::exception& e) { + m_logger(Logging::ERROR) << "Error occured during mining: " << e.what(); + m_state = MiningState::MINING_STOPPED; + } + + m_miningStopped.set(); +} + +void Miner::workerFunc(const Block& blockTemplate, difficulty_type difficulty, uint32_t nonceStep) { + try { + Block block = blockTemplate; + Crypto::cn_context cryptoContext; + + while (m_state == MiningState::MINING_IN_PROGRESS) { + Crypto::Hash hash; + if (!get_block_longhash(cryptoContext, block, hash)) { + //error occured + m_logger(Logging::DEBUGGING) << "calculating long hash error occured"; + m_state = MiningState::MINING_STOPPED; + return; + } + + if (check_hash(hash, difficulty)) { + m_logger(Logging::INFO) << "Found block for difficulty " << difficulty; + + if (!setStateBlockFound()) { + m_logger(Logging::DEBUGGING) << "block is already found or mining stopped"; + return; + } + + m_block = block; + return; + } + + block.nonce += nonceStep; + } + } catch (std::exception& e) { + m_logger(Logging::ERROR) << "Miner got error: " << e.what(); + m_state = MiningState::MINING_STOPPED; + } +} + +bool Miner::setStateBlockFound() { + auto state = m_state.load(); + + for (;;) { + switch (state) { + case MiningState::BLOCK_FOUND: + return false; + + case MiningState::MINING_IN_PROGRESS: + if (m_state.compare_exchange_weak(state, MiningState::BLOCK_FOUND)) { + return true; + } + break; + + case MiningState::MINING_STOPPED: + return false; + + default: + assert(false); + return false; + } + } +} + +} //namespace CryptoNote diff --git a/src/Miner/Miner.h b/src/Miner/Miner.h new file mode 100644 index 00000000..5f5db425 --- /dev/null +++ b/src/Miner/Miner.h @@ -0,0 +1,67 @@ +// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include +#include + +#include +#include +#include + +#include "CryptoNote.h" +#include "CryptoNoteCore/Difficulty.h" + +#include "Logging/LoggerRef.h" + +namespace CryptoNote { + +struct BlockMiningParameters { + Block blockTemplate; + difficulty_type difficulty; +}; + +class Miner { +public: + Miner(System::Dispatcher& dispatcher, Logging::ILogger& logger); + ~Miner(); + + Block mine(const BlockMiningParameters& blockMiningParameters, size_t threadCount); + + //NOTE! this is blocking method + void stop(); + +private: + System::Dispatcher& m_dispatcher; + System::Event m_miningStopped; + + enum class MiningState : uint8_t { MINING_STOPPED, BLOCK_FOUND, MINING_IN_PROGRESS}; + std::atomic m_state; + + std::vector>> m_workers; + + Block m_block; + + Logging::LoggerRef m_logger; + + void runWorkers(BlockMiningParameters blockMiningParameters, size_t threadCount); + void workerFunc(const Block& blockTemplate, difficulty_type difficulty, uint32_t nonceStep); + bool setStateBlockFound(); +}; + +} //namespace CryptoNote diff --git a/src/Miner/MinerEvent.h b/src/Miner/MinerEvent.h new file mode 100644 index 00000000..8ab85c53 --- /dev/null +++ b/src/Miner/MinerEvent.h @@ -0,0 +1,31 @@ +// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +namespace Miner { + +enum class MinerEventType: uint8_t { + BLOCK_MINED, + BLOCKCHAIN_UPDATED, +}; + +struct MinerEvent { + MinerEventType type; +}; + +} //namespace Miner diff --git a/src/Miner/MinerManager.cpp b/src/Miner/MinerManager.cpp new file mode 100644 index 00000000..613bb37c --- /dev/null +++ b/src/Miner/MinerManager.cpp @@ -0,0 +1,276 @@ +// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "MinerManager.h" + +#include +#include +#include + +#include "Common/StringTools.h" +#include "CryptoNoteConfig.h" +#include "CryptoNoteCore/CryptoNoteTools.h" +#include "CryptoNoteCore/CryptoNoteFormatUtils.h" +#include "CryptoNoteCore/TransactionExtra.h" +#include "Rpc/HttpClient.h" +#include "Rpc/CoreRpcServerCommandsDefinitions.h" +#include "Rpc/JsonRpc.h" + +using namespace CryptoNote; + +namespace Miner { + +namespace { + +MinerEvent BlockMinedEvent() { + MinerEvent event; + event.type = MinerEventType::BLOCK_MINED; + return event; +} + +MinerEvent BlockchainUpdatedEvent() { + MinerEvent event; + event.type = MinerEventType::BLOCKCHAIN_UPDATED; + return event; +} + +void adjustMergeMiningTag(Block& blockTemplate) { + if (blockTemplate.majorVersion == BLOCK_MAJOR_VERSION_2) { + CryptoNote::TransactionExtraMergeMiningTag mmTag; + mmTag.depth = 0; + if (!CryptoNote::get_aux_block_header_hash(blockTemplate, mmTag.merkleRoot)) { + throw std::runtime_error("Couldn't get block header hash"); + } + + blockTemplate.parentBlock.baseTransaction.extra.clear(); + if (!CryptoNote::appendMergeMiningTagToExtra(blockTemplate.parentBlock.baseTransaction.extra, mmTag)) { + throw std::runtime_error("Couldn't append merge mining tag"); + } + } +} + +} + +MinerManager::MinerManager(System::Dispatcher& dispatcher, const CryptoNote::MiningConfig& config, Logging::ILogger& logger) : + m_dispatcher(dispatcher), + m_logger(logger, "MinerManager"), + m_contextGroup(dispatcher), + m_config(config), + m_miner(dispatcher, logger), + m_blockchainMonitor(dispatcher, m_config.daemonHost, m_config.daemonPort, m_config.scanPeriod, logger), + m_eventOccurred(dispatcher), + m_httpEvent(dispatcher), + m_lastBlockTimestamp(0) { + + m_httpEvent.set(); +} + +MinerManager::~MinerManager() { +} + +void MinerManager::start() { + m_logger(Logging::DEBUGGING) << "starting"; + + BlockMiningParameters params; + for (;;) { + m_logger(Logging::INFO) << "requesting mining parameters"; + + try { + params = requestMiningParameters(m_dispatcher, m_config.daemonHost, m_config.daemonPort, m_config.miningAddress); + } catch (ConnectException& e) { + m_logger(Logging::WARNING) << "Couldn't connect to daemon: " << e.what(); + System::Timer timer(m_dispatcher); + timer.sleep(std::chrono::seconds(m_config.scanPeriod)); + continue; + } + + adjustBlockTemplate(params.blockTemplate); + break; + } + + startBlockchainMonitoring(); + startMining(params); + + eventLoop(); +} + +void MinerManager::eventLoop() { + size_t blocksMined = 0; + + for (;;) { + m_logger(Logging::DEBUGGING) << "waiting for event"; + MinerEvent event = waitEvent(); + + switch (event.type) { + case MinerEventType::BLOCK_MINED: { + m_logger(Logging::DEBUGGING) << "got BLOCK_MINED event"; + stopBlockchainMonitoring(); + + if (submitBlock(m_minedBlock, m_config.daemonHost, m_config.daemonPort)) { + m_lastBlockTimestamp = m_minedBlock.timestamp; + + if (m_config.blocksLimit != 0 && ++blocksMined == m_config.blocksLimit) { + m_logger(Logging::INFO) << "Miner mined requested " << m_config.blocksLimit << " blocks. Quitting"; + return; + } + } + + BlockMiningParameters params = requestMiningParameters(m_dispatcher, m_config.daemonHost, m_config.daemonPort, m_config.miningAddress); + adjustBlockTemplate(params.blockTemplate); + + startBlockchainMonitoring(); + startMining(params); + break; + } + + case MinerEventType::BLOCKCHAIN_UPDATED: { + m_logger(Logging::DEBUGGING) << "got BLOCKCHAIN_UPDATED event"; + stopMining(); + stopBlockchainMonitoring(); + BlockMiningParameters params = requestMiningParameters(m_dispatcher, m_config.daemonHost, m_config.daemonPort, m_config.miningAddress); + adjustBlockTemplate(params.blockTemplate); + + startBlockchainMonitoring(); + startMining(params); + break; + } + + default: + assert(false); + return; + } + } +} + +MinerEvent MinerManager::waitEvent() { + while(m_events.empty()) { + m_eventOccurred.wait(); + m_eventOccurred.clear(); + } + + MinerEvent event = std::move(m_events.front()); + m_events.pop(); + + return event; +} + +void MinerManager::pushEvent(MinerEvent&& event) { + m_events.push(std::move(event)); + m_eventOccurred.set(); +} + +void MinerManager::startMining(const CryptoNote::BlockMiningParameters& params) { + m_contextGroup.spawn([this, params] () { + try { + m_minedBlock = m_miner.mine(params, m_config.threadCount); + pushEvent(BlockMinedEvent()); + } catch (System::InterruptedException&) { + } catch (std::exception& e) { + m_logger(Logging::ERROR) << "Miner context unexpectedly finished: " << e.what(); + } + }); +} + +void MinerManager::stopMining() { + m_miner.stop(); +} + +void MinerManager::startBlockchainMonitoring() { + m_contextGroup.spawn([this] () { + try { + m_blockchainMonitor.waitBlockchainUpdate(); + pushEvent(BlockchainUpdatedEvent()); + } catch (System::InterruptedException&) { + } catch (std::exception& e) { + m_logger(Logging::ERROR) << "BlockchainMonitor context unexpectedly finished: " << e.what(); + } + }); +} + +void MinerManager::stopBlockchainMonitoring() { + m_blockchainMonitor.stop(); +} + +bool MinerManager::submitBlock(const Block& minedBlock, const std::string& daemonHost, uint16_t daemonPort) { + try { + HttpClient client(m_dispatcher, daemonHost, daemonPort); + + COMMAND_RPC_SUBMITBLOCK::request request; + request.emplace_back(Common::toHex(toBinaryArray(minedBlock))); + + COMMAND_RPC_SUBMITBLOCK::response response; + + System::EventLock lk(m_httpEvent); + JsonRpc::invokeJsonRpcCommand(client, "submitblock", request, response); + + m_logger(Logging::INFO) << "Block has been successfully submitted. Block hash: " << Common::podToHex(get_block_hash(minedBlock)); + return true; + } catch (std::exception& e) { + m_logger(Logging::WARNING) << "Couldn't submit block: " << Common::podToHex(get_block_hash(minedBlock)) << ", reason: " << e.what(); + return false; + } +} + +BlockMiningParameters MinerManager::requestMiningParameters(System::Dispatcher& dispatcher, const std::string& daemonHost, uint16_t daemonPort, const std::string& miningAddress) { + try { + HttpClient client(dispatcher, daemonHost, daemonPort); + + COMMAND_RPC_GETBLOCKTEMPLATE::request request; + request.wallet_address = miningAddress; + request.reserve_size = 0; + + COMMAND_RPC_GETBLOCKTEMPLATE::response response; + + System::EventLock lk(m_httpEvent); + JsonRpc::invokeJsonRpcCommand(client, "getblocktemplate", request, response); + + if (response.status != CORE_RPC_STATUS_OK) { + throw std::runtime_error("Core responded with wrong status: " + response.status); + } + + BlockMiningParameters params; + params.difficulty = response.difficulty; + + if(!fromBinaryArray(params.blockTemplate, Common::fromHex(response.blocktemplate_blob))) { + throw std::runtime_error("Couldn't deserialize block template"); + } + + m_logger(Logging::DEBUGGING) << "Requested block template with previous block hash: " << Common::podToHex(params.blockTemplate.previousBlockHash); + return params; + } catch (std::exception& e) { + m_logger(Logging::WARNING) << "Couldn't get block template: " << e.what(); + throw; + } +} + + +void MinerManager::adjustBlockTemplate(CryptoNote::Block& blockTemplate) const { + adjustMergeMiningTag(blockTemplate); + + if (m_config.firstBlockTimestamp == 0) { + //no need to fix timestamp + return; + } + + if (m_lastBlockTimestamp == 0) { + blockTemplate.timestamp = m_config.firstBlockTimestamp; + } else if (m_lastBlockTimestamp != 0 && m_config.blockTimestampInterval != 0) { + blockTemplate.timestamp = m_lastBlockTimestamp + m_config.blockTimestampInterval; + } +} + +} //namespace Miner diff --git a/src/Miner/MinerManager.h b/src/Miner/MinerManager.h new file mode 100644 index 00000000..c9cb30d0 --- /dev/null +++ b/src/Miner/MinerManager.h @@ -0,0 +1,76 @@ +// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include + +#include +#include + +#include "BlockchainMonitor.h" +#include "Logging/LoggerRef.h" +#include "Miner.h" +#include "MinerEvent.h" +#include "MiningConfig.h" + +namespace System { +class Dispatcher; +} + +namespace Miner { + +class MinerManager { +public: + MinerManager(System::Dispatcher& dispatcher, const CryptoNote::MiningConfig& config, Logging::ILogger& logger); + ~MinerManager(); + + void start(); + +private: + System::Dispatcher& m_dispatcher; + Logging::LoggerRef m_logger; + System::ContextGroup m_contextGroup; + CryptoNote::MiningConfig m_config; + CryptoNote::Miner m_miner; + BlockchainMonitor m_blockchainMonitor; + + System::Event m_eventOccurred; + System::Event m_httpEvent; + std::queue m_events; + + CryptoNote::Block m_minedBlock; + + uint64_t m_lastBlockTimestamp; + + void eventLoop(); + MinerEvent waitEvent(); + void pushEvent(MinerEvent&& event); + + void startMining(const CryptoNote::BlockMiningParameters& params); + void stopMining(); + + void startBlockchainMonitoring(); + void stopBlockchainMonitoring(); + + bool submitBlock(const CryptoNote::Block& minedBlock, const std::string& daemonHost, uint16_t daemonPort); + CryptoNote::BlockMiningParameters requestMiningParameters(System::Dispatcher& dispatcher, const std::string& daemonHost, uint16_t daemonPort, const std::string& miningAddress); + + void adjustBlockTemplate(CryptoNote::Block& blockTemplate) const; +}; + +} //namespace Miner diff --git a/src/Miner/MiningConfig.cpp b/src/Miner/MiningConfig.cpp new file mode 100644 index 00000000..b416f98e --- /dev/null +++ b/src/Miner/MiningConfig.cpp @@ -0,0 +1,138 @@ +// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "MiningConfig.h" + +#include +#include + +#include +#include +#include +#include + +#include "CryptoNoteConfig.h" +#include "Logging/ILogger.h" + +namespace po = boost::program_options; + +namespace CryptoNote { + +namespace { + +const size_t DEFAULT_SCANT_PERIOD = 30; +const char* DEFAULT_DAEMON_HOST = "127.0.0.1"; +const size_t CONCURRENCY_LEVEL = std::thread::hardware_concurrency(); + +po::options_description cmdOptions; + +void parseDaemonAddress(const std::string& daemonAddress, std::string& daemonHost, uint16_t& daemonPort) { + std::vector splittedAddress; + boost::algorithm::split(splittedAddress, daemonAddress, boost::algorithm::is_any_of(":")); + + if (splittedAddress.size() != 2) { + throw std::runtime_error("Wrong daemon address format"); + } + + if (splittedAddress[0].empty() || splittedAddress[1].empty()) { + throw std::runtime_error("Wrong daemon address format"); + } + + daemonHost = splittedAddress[0]; + + try { + daemonPort = boost::lexical_cast(splittedAddress[1]); + } catch (std::exception&) { + throw std::runtime_error("Wrong daemon address format"); + } +} + +} + +MiningConfig::MiningConfig(): help(false) { + cmdOptions.add_options() + ("help,h", "produce this help message and exit") + ("address", po::value(), "Valid cryptonote miner's address") + ("daemon-host", po::value()->default_value(DEFAULT_DAEMON_HOST), "Daemon host") + ("daemon-rpc-port", po::value()->default_value(static_cast(RPC_DEFAULT_PORT)), "Daemon's RPC port") + ("daemon-address", po::value(), "Daemon host:port. If you use this option you must not use --daemon-host and --daemon-port options") + ("threads", po::value()->default_value(CONCURRENCY_LEVEL), "Mining threads count. Must not be greater than you concurrency level. Default value is your hardware concurrency level") + ("scan-time", po::value()->default_value(DEFAULT_SCANT_PERIOD), "Blockchain polling interval (seconds). How often miner will check blockchain for updates") + ("log-level", po::value()->default_value(1), "Log level. Must be 0..5") + ("limit", po::value()->default_value(0), "Mine exact quantity of blocks. 0 means no limit") + ("first-block-timestamp", po::value()->default_value(0), "Set timestamp to the first mined block. 0 means leave timestamp unchanged") + ("block-timestamp-interval", po::value()->default_value(0), "Timestamp step for each subsequent block. May be set only if --first-block-timestamp has been set." + " If not set blocks' timestamps remain unchanged"); +} + +void MiningConfig::parse(int argc, char** argv) { + po::variables_map options; + po::store(po::parse_command_line(argc, argv, cmdOptions), options); + po::notify(options); + + if (options.count("help") != 0) { + help = true; + return; + } + + if (options.count("address") == 0) { + throw std::runtime_error("Specify --address option"); + } + + miningAddress = options["address"].as(); + + if (!options["daemon-address"].empty()) { + if (!options["daemon-host"].defaulted() || !options["daemon-rpc-port"].defaulted()) { + throw std::runtime_error("Either --daemon-host or --daemon-rpc-port is already specified. You must not specify --daemon-address"); + } + + parseDaemonAddress(options["daemon-address"].as(), daemonHost, daemonPort); + } else { + daemonHost = options["daemon-host"].as(); + daemonPort = options["daemon-rpc-port"].as(); + } + + threadCount = options["threads"].as(); + if (threadCount == 0 || threadCount > CONCURRENCY_LEVEL) { + throw std::runtime_error("--threads option must be 1.." + std::to_string(CONCURRENCY_LEVEL)); + } + + scanPeriod = options["scan-time"].as(); + if (scanPeriod == 0) { + throw std::runtime_error("--scan-time must not be zero"); + } + + logLevel = static_cast(options["log-level"].as()); + if (logLevel > static_cast(Logging::TRACE)) { + throw std::runtime_error("--log-level value is too big"); + } + + blocksLimit = options["limit"].as(); + + if (!options["block-timestamp-interval"].defaulted() && options["first-block-timestamp"].defaulted()) { + throw std::runtime_error("If you specify --block-timestamp-interval you must specify --first-block-timestamp either"); + } + + firstBlockTimestamp = options["first-block-timestamp"].as(); + blockTimestampInterval = options["block-timestamp-interval"].as(); +} + +void MiningConfig::printHelp() { + std::cout << cmdOptions << std::endl; +} + +} diff --git a/src/Miner/MiningConfig.h b/src/Miner/MiningConfig.h new file mode 100644 index 00000000..3f969f2a --- /dev/null +++ b/src/Miner/MiningConfig.h @@ -0,0 +1,43 @@ +// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include +#include + +namespace CryptoNote { + +struct MiningConfig { + MiningConfig(); + + void parse(int argc, char** argv); + void printHelp(); + + std::string miningAddress; + std::string daemonHost; + uint16_t daemonPort; + size_t threadCount; + size_t scanPeriod; + uint8_t logLevel; + size_t blocksLimit; + uint64_t firstBlockTimestamp; + int64_t blockTimestampInterval; + bool help; +}; + +} //namespace CryptoNote diff --git a/src/Miner/main.cpp b/src/Miner/main.cpp new file mode 100644 index 00000000..73c462cf --- /dev/null +++ b/src/Miner/main.cpp @@ -0,0 +1,52 @@ +// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "Common/SignalHandler.h" + +#include "Logging/LoggerGroup.h" +#include "Logging/ConsoleLogger.h" +#include "Logging/LoggerRef.h" + +#include "MinerManager.h" + +#include + +int main(int argc, char** argv) { + try { + CryptoNote::MiningConfig config; + config.parse(argc, argv); + + if (config.help) { + config.printHelp(); + return 0; + } + + Logging::LoggerGroup loggerGroup; + Logging::ConsoleLogger consoleLogger(static_cast(config.logLevel)); + loggerGroup.addLogger(consoleLogger); + + System::Dispatcher dispatcher; + Miner::MinerManager app(dispatcher, config, loggerGroup); + + app.start(); + } catch (std::exception& e) { + std::cerr << "Fatal: " << e.what() << std::endl; + return 1; + } + + return 0; +} diff --git a/src/NodeRpcProxy/NodeRpcProxy.cpp b/src/NodeRpcProxy/NodeRpcProxy.cpp index cab093ba..c70e5240 100644 --- a/src/NodeRpcProxy/NodeRpcProxy.cpp +++ b/src/NodeRpcProxy/NodeRpcProxy.cpp @@ -225,36 +225,36 @@ void NodeRpcProxy::updateBlockchainStatus() { m_lastKnowHash = blockHash; m_nodeHeight.store(static_cast(rsp.block_header.height), std::memory_order_relaxed); m_lastLocalBlockTimestamp.store(rsp.block_header.timestamp, std::memory_order_relaxed); - // TODO request and update network height - m_networkHeight.store(static_cast(rsp.block_header.height), std::memory_order_relaxed); - m_observerManager.notify(&INodeObserver::lastKnownBlockHeightUpdated, m_networkHeight.load(std::memory_order_relaxed)); - //if (m_networkHeight.load(std::memory_order_relaxed) != rsp.block_header.network_height) { - // m_networkHeight.store(rsp.block_header.height, std::memory_order_relaxed); - // m_observerManager.notify(&INodeObserver::lastKnownBlockHeightUpdated, m_networkHeight); - //} m_observerManager.notify(&INodeObserver::localBlockchainUpdated, m_nodeHeight.load(std::memory_order_relaxed)); } } - updatePeerCount(); + CryptoNote::COMMAND_RPC_GET_INFO::request getInfoReq = AUTO_VAL_INIT(getInfoReq); + CryptoNote::COMMAND_RPC_GET_INFO::response getInfoResp = AUTO_VAL_INIT(getInfoResp); + + ec = jsonCommand("/getinfo", getInfoReq, getInfoResp); + if (!ec) { + //a quirk to let wallets work with previous versions daemons. + //Previous daemons didn't have the 'last_known_block_index' parameter in RPC so it may have zero value. + auto lastKnownBlockIndex = std::max(getInfoResp.last_known_block_index, m_nodeHeight.load(std::memory_order_relaxed)); + if (m_networkHeight.load(std::memory_order_relaxed) != lastKnownBlockIndex) { + m_networkHeight.store(lastKnownBlockIndex, std::memory_order_relaxed); + m_observerManager.notify(&INodeObserver::lastKnownBlockHeightUpdated, m_networkHeight.load(std::memory_order_relaxed)); + } + + updatePeerCount(getInfoResp.incoming_connections_count + getInfoResp.outgoing_connections_count); + } + if (m_connected != m_httpClient->isConnected()) { m_connected = m_httpClient->isConnected(); m_rpcProxyObserverManager.notify(&INodeRpcProxyObserver::connectionStatusUpdated, m_connected); } } -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); - - std::error_code ec = jsonCommand("/getinfo", req, rsp); - - 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.load(std::memory_order_relaxed)); - } +void NodeRpcProxy::updatePeerCount(size_t peerCount) { + if (peerCount != m_peerCount) { + m_peerCount = peerCount; + m_observerManager.notify(&INodeObserver::peerCountUpdated, m_peerCount.load(std::memory_order_relaxed)); } } @@ -306,7 +306,7 @@ uint32_t NodeRpcProxy::getLocalBlockCount() const { } uint32_t NodeRpcProxy::getKnownBlockCount() const { - return m_networkHeight.load(std::memory_order_relaxed); + return m_networkHeight.load(std::memory_order_relaxed) + 1; } uint64_t NodeRpcProxy::getLastLocalBlockTimestamp() const { diff --git a/src/NodeRpcProxy/NodeRpcProxy.h b/src/NodeRpcProxy/NodeRpcProxy.h index 20848475..aac6b44e 100644 --- a/src/NodeRpcProxy/NodeRpcProxy.h +++ b/src/NodeRpcProxy/NodeRpcProxy.h @@ -92,7 +92,7 @@ private: void updateNodeStatus(); void updateBlockchainStatus(); bool updatePoolStatus(); - void updatePeerCount(); + void updatePeerCount(size_t peerCount); void updatePoolState(const std::vector>& addedTxs, const std::vector& deletedTxsIds); std::error_code doRelayTransaction(const CryptoNote::Transaction& transaction); diff --git a/src/PaymentGate/PaymentServiceJsonRpcMessages.cpp b/src/PaymentGate/PaymentServiceJsonRpcMessages.cpp index b7a847b8..62f40a6c 100755 --- a/src/PaymentGate/PaymentServiceJsonRpcMessages.cpp +++ b/src/PaymentGate/PaymentServiceJsonRpcMessages.cpp @@ -20,138 +20,114 @@ namespace PaymentService { -void TransferDestination::serialize(CryptoNote::ISerializer& serializer) { - bool r = serializer(amount, "amount"); - r &= serializer(address, "address"); +void Reset::Request::serialize(CryptoNote::ISerializer& serializer) { + serializer(viewSecretKey, "viewSecretKey"); +} - if (!r) { +void Reset::Response::serialize(CryptoNote::ISerializer& serializer) { +} + +void GetViewKey::Request::serialize(CryptoNote::ISerializer& serializer) { +} + +void GetViewKey::Response::serialize(CryptoNote::ISerializer& serializer) { + serializer(viewSecretKey, "viewSecretKey"); +} + +void GetStatus::Request::serialize(CryptoNote::ISerializer& serializer) { +} + +void GetStatus::Response::serialize(CryptoNote::ISerializer& serializer) { + serializer(blockCount, "blockCount"); + serializer(knownBlockCount, "knownBlockCount"); + serializer(lastBlockHash, "lastBlockHash"); + serializer(peerCount, "peerCount"); +} + +void GetAddresses::Request::serialize(CryptoNote::ISerializer& serializer) { +} + +void GetAddresses::Response::serialize(CryptoNote::ISerializer& serializer) { + serializer(addresses, "addresses"); +} + +void CreateAddress::Request::serialize(CryptoNote::ISerializer& serializer) { + bool hasSecretKey = serializer(spendSecretKey, "spendSecretKey"); + bool hasPublicKey = serializer(spendPublicKey, "spendPublicKey"); + + if (hasSecretKey && hasPublicKey) { + //TODO: replace it with error codes throw RequestSerializationError(); } } -void SendTransactionRequest::serialize(CryptoNote::ISerializer& serializer) { - bool r = serializer(destinations, "destinations"); - r &= serializer(fee, "fee"); - r &= serializer(mixin, "mixin"); - serializer(unlockTime, "unlock_time"); - serializer(paymentId, "payment_id"); - - if (!r) { - throw RequestSerializationError(); - } -} - -void SendTransactionResponse::serialize(CryptoNote::ISerializer& serializer) { - serializer(transactionId, "transaction_id"); -} - -void GetAddressRequest::serialize(CryptoNote::ISerializer& serializer) { - serializer(index, "index"); -} - -void DeleteAddressRequest::serialize(CryptoNote::ISerializer& serializer) { - bool r = serializer(address, "address"); - - if (!r) { - throw RequestSerializationError(); - } -} - -void DeleteAddressResponse::serialize(CryptoNote::ISerializer& serializer) { -} - -void CreateAddressResponse::serialize(CryptoNote::ISerializer& serializer) { +void CreateAddress::Response::serialize(CryptoNote::ISerializer& serializer) { serializer(address, "address"); } -void GetAddressCountResponse::serialize(CryptoNote::ISerializer& serializer) { - serializer(count, "count"); -} - -void GetAddressResponse::serialize(CryptoNote::ISerializer& serializer) { - serializer(address, "address"); -} - -void GetActualBalanceRequest::serialize(CryptoNote::ISerializer& serializer) { - bool r = serializer(address, "address"); - - if (!r) { - throw std::runtime_error("Required parameter is missing"); +void DeleteAddress::Request::serialize(CryptoNote::ISerializer& serializer) { + if (!serializer(address, "address")) { + throw RequestSerializationError(); } } -void GetPendingBalanceRequest::serialize(CryptoNote::ISerializer& serializer) { +void DeleteAddress::Response::serialize(CryptoNote::ISerializer& serializer) { +} + +void GetSpendKeys::Request::serialize(CryptoNote::ISerializer& serializer) { + if (!serializer(address, "address")) { + throw RequestSerializationError(); + } +} + +void GetSpendKeys::Response::serialize(CryptoNote::ISerializer& serializer) { + serializer(spendSecretKey, "spendSecretKey"); + serializer(spendPublicKey, "spendPublicKey"); +} + +void GetBalance::Request::serialize(CryptoNote::ISerializer& serializer) { serializer(address, "address"); } -void GetActualBalanceResponse::serialize(CryptoNote::ISerializer& serializer) { - serializer(actualBalance, "actual_balance"); +void GetBalance::Response::serialize(CryptoNote::ISerializer& serializer) { + serializer(availableBalance, "availableBalance"); + serializer(lockedAmount, "lockedAmount"); } -void GetPendingBalanceResponse::serialize(CryptoNote::ISerializer& serializer) { - serializer(pendingBalance, "pending_balance"); -} - -void GetTransactionsCountResponse::serialize(CryptoNote::ISerializer& serializer) { - serializer(transactionsCount, "transactions_count"); -} - -void GetTransfersCountResponse::serialize(CryptoNote::ISerializer& serializer) { - serializer(transfersCount, "transfers_count"); -} - -void GetTransactionIdByTransferIdRequest::serialize(CryptoNote::ISerializer& serializer) { - bool r = serializer(transferId, "transfer_id"); +void GetBlockHashes::Request::serialize(CryptoNote::ISerializer& serializer) { + bool r = serializer(firstBlockIndex, "firstBlockIndex"); + r &= serializer(blockCount, "blockCount"); if (!r) { throw RequestSerializationError(); } } -void GetTransactionIdByTransferIdResponse::serialize(CryptoNote::ISerializer& serializer) { - serializer(transactionid, "transaction_id"); +void GetBlockHashes::Response::serialize(CryptoNote::ISerializer& serializer) { + serializer(blockHashes, "blockHashes"); } -void GetTransactionRequest::serialize(CryptoNote::ISerializer& serializer) { - bool r = serializer(transactionId, "transaction_id"); +void TransactionHashesInBlockRpcInfo::serialize(CryptoNote::ISerializer& serializer) { + serializer(blockHash, "blockHash"); + serializer(transactionHashes, "transactionHashes"); +} - if (!r) { +void GetTransactionHashes::Request::serialize(CryptoNote::ISerializer& serializer) { + serializer(addresses, "addresses"); + + if (serializer(blockHash, "blockHash") == serializer(firstBlockIndex, "firstBlockIndex")) { throw RequestSerializationError(); } -} -void TransactionRpcInfo::serialize(CryptoNote::ISerializer& serializer) { - serializer(firstTransferId, "first_transfer_id"); - serializer(transferCount, "transfer_count"); - serializer(totalAmount, "total_amount"); - serializer(fee, "fee"); - serializer(hash, "hash"); - serializer(blockHeight, "block_height"); - serializer(state, "state"); - serializer(timestamp, "timestamp"); - serializer(extra, "extra"); - serializer(transfers, "transfers"); -} - -void GetTransactionResponse::serialize(CryptoNote::ISerializer& serializer) { - serializer(found, "found"); - - if (found) { - serializer(transactionInfo, "transaction_info"); + if (!serializer(blockCount, "blockCount")) { + throw RequestSerializationError(); } + + serializer(paymentId, "paymentId"); } -void ListTransactionsRequest::serialize(CryptoNote::ISerializer& serializer) { - bool r = serializer(startingTransactionId, "starting_transaction_id"); - r &= serializer(maxTransactionCount, "max_transaction_count"); - - if (!r) { - throw std::runtime_error("Required parameter is missing"); - } -} - -void ListTransactionsResponse::serialize(CryptoNote::ISerializer& serializer) { - serializer(transactions, "transactions"); +void GetTransactionHashes::Response::serialize(CryptoNote::ISerializer& serializer) { + serializer(items, "items"); } void TransferRpcInfo::serialize(CryptoNote::ISerializer& serializer) { @@ -160,43 +136,155 @@ void TransferRpcInfo::serialize(CryptoNote::ISerializer& serializer) { serializer(amount, "amount"); } -void GetTransferRequest::serialize(CryptoNote::ISerializer& serializer) { - bool r = serializer(transferId, "transfer_id"); - - if (!r) { - throw RequestSerializationError(); - } -} - -void GetTransferResponse::serialize(CryptoNote::ISerializer& serializer) { - serializer(found, "found"); - if (found) { - serializer(transferInfo, "transfer_info"); - } -} - -void GetIncomingPaymentsRequest::serialize(CryptoNote::ISerializer& serializer) { - bool r = serializer(payments, "payments"); - - if (!r) { - throw RequestSerializationError(); - } -} - -void PaymentsById::serialize(CryptoNote::ISerializer& serializer) { - serializer(id, "id"); - serializer(payments, "payments"); -} - -void GetIncomingPaymentsResponse::serialize(CryptoNote::ISerializer& serializer) { - serializer(payments, "payments"); -} - -void PaymentDetails::serialize(CryptoNote::ISerializer& serializer) { - serializer(txHash, "tx_hash"); +void TransactionRpcInfo::serialize(CryptoNote::ISerializer& serializer) { + serializer(state, "state"); + serializer(transactionHash, "transactionHash"); + serializer(blockIndex, "blockIndex"); + serializer(timestamp, "timestamp"); + serializer(isBase, "isBase"); + serializer(unlockTime, "unlockTime"); serializer(amount, "amount"); - serializer(blockHeight, "block_height"); - serializer(unlockTime, "unlock_time"); + serializer(fee, "fee"); + serializer(transfers, "transfers"); + serializer(extra, "extra"); + serializer(paymentId, "paymentId"); +} + +void GetTransaction::Request::serialize(CryptoNote::ISerializer& serializer) { + if (!serializer(transactionHash, "transactionHash")) { + throw RequestSerializationError(); + } +} + +void GetTransaction::Response::serialize(CryptoNote::ISerializer& serializer) { + serializer(transaction, "transaction"); +} + +void TransactionsInBlockRpcInfo::serialize(CryptoNote::ISerializer& serializer) { + serializer(blockHash, "blockHash"); + serializer(transactions, "transactions"); +} + +void GetTransactions::Request::serialize(CryptoNote::ISerializer& serializer) { + serializer(addresses, "addresses"); + + if (serializer(blockHash, "blockHash") == serializer(firstBlockIndex, "firstBlockIndex")) { + throw RequestSerializationError(); + } + + if (!serializer(blockCount, "blockCount")) { + throw RequestSerializationError(); + } + + serializer(paymentId, "paymentId"); +} + +void GetTransactions::Response::serialize(CryptoNote::ISerializer& serializer) { + serializer(items, "items"); +} + +void GetUnconfirmedTransactionHashes::Request::serialize(CryptoNote::ISerializer& serializer) { + serializer(addresses, "addresses"); +} + +void GetUnconfirmedTransactionHashes::Response::serialize(CryptoNote::ISerializer& serializer) { + serializer(transactionHashes, "transactionHashes"); +} + +void WalletRpcOrder::serialize(CryptoNote::ISerializer& serializer) { + bool r = serializer(address, "address"); + r &= serializer(amount, "amount"); + + if (!r) { + throw RequestSerializationError(); + } +} + +void SendTransaction::Request::serialize(CryptoNote::ISerializer& serializer) { + serializer(sourceAddresses, "addresses"); + + if (!serializer(transfers, "transfers")) { + throw RequestSerializationError(); + } + + serializer(changeAddress, "changeAddress"); + + if (!serializer(fee, "fee")) { + throw RequestSerializationError(); + } + + if (!serializer(anonymity, "anonymity")) { + throw RequestSerializationError(); + } + + bool hasExtra = serializer(extra, "extra"); + bool hasPaymentId = serializer(paymentId, "paymentId"); + + if (hasExtra && hasPaymentId) { + throw RequestSerializationError(); + } + + serializer(unlockTime, "unlockTime"); +} + +void SendTransaction::Response::serialize(CryptoNote::ISerializer& serializer) { + serializer(transactionHash, "transactionHash"); +} + +void CreateDelayedTransaction::Request::serialize(CryptoNote::ISerializer& serializer) { + serializer(addresses, "addresses"); + + if (!serializer(transfers, "transfers")) { + throw RequestSerializationError(); + } + + serializer(changeAddress, "changeAddress"); + + if (!serializer(fee, "fee")) { + throw RequestSerializationError(); + } + + if (!serializer(anonymity, "anonymity")) { + throw RequestSerializationError(); + } + + bool hasExtra = serializer(extra, "extra"); + bool hasPaymentId = serializer(paymentId, "paymentId"); + + if (hasExtra && hasPaymentId) { + throw RequestSerializationError(); + } + + serializer(unlockTime, "unlockTime"); +} + +void CreateDelayedTransaction::Response::serialize(CryptoNote::ISerializer& serializer) { + serializer(transactionHash, "transactionHash"); +} + +void GetDelayedTransactionHashes::Request::serialize(CryptoNote::ISerializer& serializer) { +} + +void GetDelayedTransactionHashes::Response::serialize(CryptoNote::ISerializer& serializer) { + serializer(transactionHashes, "transactionHashes"); +} + +void DeleteDelayedTransaction::Request::serialize(CryptoNote::ISerializer& serializer) { + if (!serializer(transactionHash, "transactionHash")) { + throw RequestSerializationError(); + } +} + +void DeleteDelayedTransaction::Response::serialize(CryptoNote::ISerializer& serializer) { +} + +void SendDelayedTransaction::Request::serialize(CryptoNote::ISerializer& serializer) { + if (!serializer(transactionHash, "transactionHash")) { + throw RequestSerializationError(); + } +} + +void SendDelayedTransaction::Response::serialize(CryptoNote::ISerializer& serializer) { } } diff --git a/src/PaymentGate/PaymentServiceJsonRpcMessages.h b/src/PaymentGate/PaymentServiceJsonRpcMessages.h index 35ed95ec..093960e4 100644 --- a/src/PaymentGate/PaymentServiceJsonRpcMessages.h +++ b/src/PaymentGate/PaymentServiceJsonRpcMessages.h @@ -18,122 +18,166 @@ #pragma once #include +#include #include #include "Serialization/ISerializer.h" namespace PaymentService { +const uint32_t DEFAULT_ANONYMITY_LEVEL = 6; + class RequestSerializationError: public std::exception { public: virtual const char* what() const throw() override { return "Request error"; } }; -struct TransferDestination { - uint64_t amount; - std::string address; +struct Reset { + struct Request { + std::string viewSecretKey; + + void serialize(CryptoNote::ISerializer& serializer); + }; + + struct Response { + void serialize(CryptoNote::ISerializer& serializer); + }; +}; + +struct GetViewKey { + struct Request { + void serialize(CryptoNote::ISerializer& serializer); + }; + + struct Response { + std::string viewSecretKey; + + void serialize(CryptoNote::ISerializer& serializer); + }; +}; + +struct GetStatus { + struct Request { + void serialize(CryptoNote::ISerializer& serializer); + }; + + struct Response { + uint32_t blockCount; + uint32_t knownBlockCount; + std::string lastBlockHash; + uint32_t peerCount; + + void serialize(CryptoNote::ISerializer& serializer); + }; +}; + +struct GetAddresses { + struct Request { + void serialize(CryptoNote::ISerializer& serializer); + }; + + struct Response { + std::vector addresses; + + void serialize(CryptoNote::ISerializer& serializer); + }; +}; + +struct CreateAddress { + struct Request { + std::string spendSecretKey; + std::string spendPublicKey; + + void serialize(CryptoNote::ISerializer& serializer); + }; + + struct Response { + std::string address; + + void serialize(CryptoNote::ISerializer& serializer); + }; +}; + +struct DeleteAddress { + struct Request { + std::string address; + + void serialize(CryptoNote::ISerializer& serializer); + }; + + struct Response { + void serialize(CryptoNote::ISerializer& serializer); + }; +}; + +struct GetSpendKeys { + struct Request { + std::string address; + + void serialize(CryptoNote::ISerializer& serializer); + }; + + struct Response { + std::string spendSecretKey; + std::string spendPublicKey; + + void serialize(CryptoNote::ISerializer& serializer); + }; +}; + +struct GetBalance { + struct Request { + std::string address; + + void serialize(CryptoNote::ISerializer& serializer); + }; + + struct Response { + uint64_t availableBalance; + uint64_t lockedAmount; + + void serialize(CryptoNote::ISerializer& serializer); + }; +}; + +struct GetBlockHashes { + struct Request { + uint32_t firstBlockIndex; + uint32_t blockCount; + + void serialize(CryptoNote::ISerializer& serializer); + }; + + struct Response { + std::vector blockHashes; + + void serialize(CryptoNote::ISerializer& serializer); + }; +}; + +struct TransactionHashesInBlockRpcInfo { + std::string blockHash; + std::vector transactionHashes; void serialize(CryptoNote::ISerializer& serializer); }; -struct SendTransactionRequest { - SendTransactionRequest() : unlockTime(0) {} +struct GetTransactionHashes { + struct Request { + std::vector addresses; + std::string blockHash; + uint32_t firstBlockIndex = std::numeric_limits::max(); + uint32_t blockCount; + std::string paymentId; - std::vector destinations; - uint64_t fee; - uint64_t mixin; - uint64_t unlockTime; - std::string paymentId; + void serialize(CryptoNote::ISerializer& serializer); + }; - void serialize(CryptoNote::ISerializer& serializer); -}; + struct Response { + std::vector items; -struct SendTransactionResponse { - uint64_t transactionId; - void serialize(CryptoNote::ISerializer& serializer); -}; - -struct GetAddressRequest { - GetAddressRequest() : index(0) {} - - size_t index; - void serialize(CryptoNote::ISerializer& serializer); -}; - -struct GetAddressCountResponse { - std::size_t count; - void serialize(CryptoNote::ISerializer& serializer); -}; - -struct DeleteAddressRequest { - std::string address; - void serialize(CryptoNote::ISerializer& serializer); -}; - -struct DeleteAddressResponse { - void serialize(CryptoNote::ISerializer& serializer); -}; - -struct CreateAddressResponse { - std::string address; - void serialize(CryptoNote::ISerializer& serializer); -}; - -struct GetAddressResponse { - std::string address; - void serialize(CryptoNote::ISerializer& serializer); -}; - -struct GetActualBalanceRequest { - std::string address; - void serialize(CryptoNote::ISerializer& serializer); -}; - -struct GetActualBalanceResponse { - uint64_t actualBalance; - - void serialize(CryptoNote::ISerializer& serializer); -}; - -struct GetPendingBalanceRequest { - std::string address; - void serialize(CryptoNote::ISerializer& serializer); -}; - -struct GetPendingBalanceResponse { - uint64_t pendingBalance; - - void serialize(CryptoNote::ISerializer& serializer); -}; - -struct GetTransactionsCountResponse { - uint64_t transactionsCount; - - void serialize(CryptoNote::ISerializer& serializer); -}; - -struct GetTransfersCountResponse { - uint64_t transfersCount; - - void serialize(CryptoNote::ISerializer& serializer); -}; - -struct GetTransactionIdByTransferIdRequest { - uint64_t transferId; - - void serialize(CryptoNote::ISerializer& serializer); -}; - -struct GetTransactionIdByTransferIdResponse { - uint64_t transactionid; - - void serialize(CryptoNote::ISerializer& serializer); -}; - -struct GetTransactionRequest { - uint64_t transactionId; - - void serialize(CryptoNote::ISerializer& serializer); + void serialize(CryptoNote::ISerializer& serializer); + }; }; struct TransferRpcInfo { @@ -145,80 +189,157 @@ struct TransferRpcInfo { }; struct TransactionRpcInfo { - uint64_t firstTransferId; - uint64_t transferCount; - int64_t totalAmount; - uint64_t fee; - std::string hash; - uint64_t blockHeight; - uint64_t timestamp; - std::string extra; uint8_t state; + std::string transactionHash; + uint32_t blockIndex; + uint64_t timestamp; + bool isBase; + uint64_t unlockTime; + int64_t amount; + uint64_t fee; std::vector transfers; + std::string extra; + std::string paymentId; void serialize(CryptoNote::ISerializer& serializer); }; -struct GetTransactionResponse { - bool found; - TransactionRpcInfo transactionInfo; +struct GetTransaction { + struct Request { + std::string transactionHash; - void serialize(CryptoNote::ISerializer& serializer); + void serialize(CryptoNote::ISerializer& serializer); + }; + + struct Response { + TransactionRpcInfo transaction; + + void serialize(CryptoNote::ISerializer& serializer); + }; }; -struct ListTransactionsRequest { - uint32_t startingTransactionId; - uint32_t maxTransactionCount; - - void serialize(CryptoNote::ISerializer& serializer); -}; - -struct ListTransactionsResponse { +struct TransactionsInBlockRpcInfo { + std::string blockHash; std::vector transactions; void serialize(CryptoNote::ISerializer& serializer); }; -struct GetTransferRequest { - uint64_t transferId; +struct GetTransactions { + struct Request { + std::vector addresses; + std::string blockHash; + uint32_t firstBlockIndex = std::numeric_limits::max(); + uint32_t blockCount; + std::string paymentId; - void serialize(CryptoNote::ISerializer& serializer); + void serialize(CryptoNote::ISerializer& serializer); + }; + + struct Response { + std::vector items; + + void serialize(CryptoNote::ISerializer& serializer); + }; }; -struct GetTransferResponse { - bool found; - TransferRpcInfo transferInfo; +struct GetUnconfirmedTransactionHashes { + struct Request { + std::vector addresses; - void serialize(CryptoNote::ISerializer& serializer); + void serialize(CryptoNote::ISerializer& serializer); + }; + + struct Response { + std::vector transactionHashes; + + void serialize(CryptoNote::ISerializer& serializer); + }; }; -struct GetIncomingPaymentsRequest { - std::vector payments; - - void serialize(CryptoNote::ISerializer& serializer); -}; - -struct PaymentDetails -{ - std::string txHash; +struct WalletRpcOrder { + std::string address; uint64_t amount; - uint64_t blockHeight; - uint64_t unlockTime; void serialize(CryptoNote::ISerializer& serializer); }; -struct PaymentsById { - std::string id; - std::vector payments; +struct SendTransaction { + struct Request { + std::vector sourceAddresses; + std::vector transfers; + std::string changeAddress; + uint64_t fee = 0; + uint32_t anonymity = DEFAULT_ANONYMITY_LEVEL; + std::string extra; + std::string paymentId; + uint64_t unlockTime = 0; - void serialize(CryptoNote::ISerializer& serializer); + void serialize(CryptoNote::ISerializer& serializer); + }; + + struct Response { + std::string transactionHash; + + void serialize(CryptoNote::ISerializer& serializer); + }; }; -struct GetIncomingPaymentsResponse { - std::vector payments; +struct CreateDelayedTransaction { + struct Request { + std::vector addresses; + std::vector transfers; + std::string changeAddress; + uint64_t fee = 0; + uint32_t anonymity = DEFAULT_ANONYMITY_LEVEL; + std::string extra; + std::string paymentId; + uint64_t unlockTime = 0; - void serialize(CryptoNote::ISerializer& serializer); + void serialize(CryptoNote::ISerializer& serializer); + }; + + struct Response { + std::string transactionHash; + + void serialize(CryptoNote::ISerializer& serializer); + }; +}; + +struct GetDelayedTransactionHashes { + struct Request { + void serialize(CryptoNote::ISerializer& serializer); + }; + + struct Response { + std::vector transactionHashes; + + void serialize(CryptoNote::ISerializer& serializer); + }; +}; + +struct DeleteDelayedTransaction { + struct Request { + std::string transactionHash; + + void serialize(CryptoNote::ISerializer& serializer); + }; + + struct Response { + void serialize(CryptoNote::ISerializer& serializer); + }; +}; + +struct SendDelayedTransaction { + struct Request { + std::string transactionHash; + + void serialize(CryptoNote::ISerializer& serializer); + }; + + struct Response { + void serialize(CryptoNote::ISerializer& serializer); + }; }; } //namespace PaymentService diff --git a/src/PaymentGate/PaymentServiceJsonRpcServer.cpp b/src/PaymentGate/PaymentServiceJsonRpcServer.cpp index 8b8c640a..fad98fbe 100755 --- a/src/PaymentGate/PaymentServiceJsonRpcServer.cpp +++ b/src/PaymentGate/PaymentServiceJsonRpcServer.cpp @@ -17,10 +17,11 @@ #include "PaymentServiceJsonRpcServer.h" +#include + #include "PaymentServiceJsonRpcMessages.h" #include "WalletService.h" -#include "Common/JsonValue.h" #include "Serialization/JsonInputValueSerializer.h" #include "Serialization/JsonOutputStreamSerializer.h" @@ -31,299 +32,157 @@ PaymentServiceJsonRpcServer::PaymentServiceJsonRpcServer(System::Dispatcher& sys , service(service) , logger(loggerGroup, "PaymentServiceJsonRpcServer") { + handlers.emplace("reset", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleReset, this, std::placeholders::_1, std::placeholders::_2))); + handlers.emplace("createAddress", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleCreateAddress, this, std::placeholders::_1, std::placeholders::_2))); + handlers.emplace("deleteAddress", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleDeleteAddress, this, std::placeholders::_1, std::placeholders::_2))); + handlers.emplace("getSpendKeys", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleGetSpendKeys, this, std::placeholders::_1, std::placeholders::_2))); + handlers.emplace("getBalance", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleGetBalance, this, std::placeholders::_1, std::placeholders::_2))); + handlers.emplace("getBlockHashes", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleGetBlockHashes, this, std::placeholders::_1, std::placeholders::_2))); + handlers.emplace("getTransactionHashes", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleGetTransactionHashes, this, std::placeholders::_1, std::placeholders::_2))); + handlers.emplace("getTransactions", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleGetTransactions, this, std::placeholders::_1, std::placeholders::_2))); + handlers.emplace("getUnconfirmedTransactionHashes", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleGetUnconfirmedTransactionHashes, this, std::placeholders::_1, std::placeholders::_2))); + handlers.emplace("getTransaction", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleGetTransaction, this, std::placeholders::_1, std::placeholders::_2))); + handlers.emplace("sendTransaction", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleSendTransaction, this, std::placeholders::_1, std::placeholders::_2))); + handlers.emplace("createDelayedTransaction", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleCreateDelayedTransaction, this, std::placeholders::_1, std::placeholders::_2))); + handlers.emplace("getDelayedTransactionHashes", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleGetDelayedTransactionHashes, this, std::placeholders::_1, std::placeholders::_2))); + handlers.emplace("deleteDelayedTransaction", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleDeleteDelayedTransaction, this, std::placeholders::_1, std::placeholders::_2))); + handlers.emplace("sendDelayedTransaction", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleSendDelayedTransaction, this, std::placeholders::_1, std::placeholders::_2))); + handlers.emplace("getViewKey", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleGetViewKey, this, std::placeholders::_1, std::placeholders::_2))); + handlers.emplace("getStatus", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleGetStatus, this, std::placeholders::_1, std::placeholders::_2))); + handlers.emplace("getAddresses", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleGetAddresses, this, std::placeholders::_1, std::placeholders::_2))); } void PaymentServiceJsonRpcServer::processJsonRpcRequest(const Common::JsonValue& req, Common::JsonValue& resp) { try { prepareJsonResponse(req, resp); + if (!req.contains("method")) { + logger(Logging::WARNING) << "Field \"method\" is not found in json request: " << req; + makeGenericErrorReponse(resp, "Invalid Request", -3600); + return; + } + + if (!req("method").isString()) { + logger(Logging::WARNING) << "Field \"method\" is not a string type: " << req; + makeGenericErrorReponse(resp, "Invalid Request", -3600); + return; + } + std::string method = req("method").getString(); - CryptoNote::JsonOutputStreamSerializer outputSerializer; - - if (method == "send_transaction") { - SendTransactionRequest sendReq; - SendTransactionResponse sendResp; - - //XXX: refactor it when migrate to different exception types in different subsystems! - try { - CryptoNote::JsonInputValueSerializer inputSerializer(req("params")); - serialize(sendReq, inputSerializer); - } catch (std::exception&) { - makeGenericErrorReponse(resp, "Invalid Request", -32600); - return; - } - - std::error_code ec = service.sendTransaction(sendReq, sendResp); - if (ec) { - makeErrorResponse(ec, resp); - return; - } - - serialize(sendResp, outputSerializer); - } else if (method == "get_address") { - GetAddressRequest getAddrReq; - GetAddressResponse getAddrResp; - - //XXX: refactor it when migrate to different exception types in different subsystems! - try { - CryptoNote::JsonInputValueSerializer inputSerializer(req("params")); - serialize(getAddrReq, inputSerializer); - } catch (std::exception&) { - makeGenericErrorReponse(resp, "Invalid Request", -32600); - return; - } - - std::error_code ec = service.getAddress(getAddrReq.index, getAddrResp.address); - if (ec) { - makeErrorResponse(ec, resp); - return; - } - - serialize(getAddrResp, outputSerializer); - } else if (method == "create_address") { - CreateAddressResponse createAddrResp; - - std::error_code ec = service.createAddress(createAddrResp.address); - if (ec) { - makeErrorResponse(ec, resp); - return; - } - - serialize(createAddrResp, outputSerializer); - } else if (method == "get_address_count") { - GetAddressCountResponse addressCountResp; - - std::error_code ec = service.getAddressCount(addressCountResp.count); - if (ec) { - makeErrorResponse(ec, resp); - return; - } - - serialize(addressCountResp, outputSerializer); - } else if (method == "delete_address") { - DeleteAddressRequest delAddrReq; - DeleteAddressResponse delAddrResp; - - //XXX: refactor it when migrate to different exception types in different subsystems! - try { - CryptoNote::JsonInputValueSerializer inputSerializer(req("params")); - serialize(delAddrReq, inputSerializer); - } catch (std::exception&) { - makeGenericErrorReponse(resp, "Invalid Request", -32600); - return; - } - - std::error_code ec = service.deleteAddress(delAddrReq.address); - if (ec) { - makeErrorResponse(ec, resp); - return; - } - - serialize(delAddrResp, outputSerializer); - } else if (method == "get_actual_balance") { - GetActualBalanceRequest actualReq; - GetActualBalanceResponse actualResp; - - //XXX: refactor it when migrate to different exception types in different subsystems! - try { - CryptoNote::JsonInputValueSerializer inputSerializer(req("params")); - serialize(actualReq, inputSerializer); - } catch (std::exception&) { - makeGenericErrorReponse(resp, "Invalid Request", -32600); - return; - } - - std::error_code ec; - if (actualReq.address == "") { - ec = service.getActualBalance(actualResp.actualBalance); - } else { - ec = service.getActualBalance(actualReq.address, actualResp.actualBalance); - } - - if (ec) { - makeErrorResponse(ec, resp); - return; - } - - serialize(actualResp, outputSerializer); - } else if (method == "get_pending_balance") { - GetPendingBalanceRequest pendingReq; - GetPendingBalanceResponse pendingResp; - - //XXX: refactor it when migrate to different exception types in different subsystems! - try { - CryptoNote::JsonInputValueSerializer inputSerializer(req("params")); - serialize(pendingReq, inputSerializer); - } catch (std::exception&) { - makeGenericErrorReponse(resp, "Invalid Request", -32600); - return; - } - - std::error_code ec; - if (pendingReq.address == "") { - ec = service.getPendingBalance(pendingResp.pendingBalance); - } else { - ec = service.getPendingBalance(pendingReq.address, pendingResp.pendingBalance); - } - - if (ec) { - makeErrorResponse(ec, resp); - return; - } - - serialize(pendingResp, outputSerializer); - } else if (method == "get_transactions_count") { - GetTransactionsCountResponse txResp; - - std::error_code ec = service.getTransactionsCount(txResp.transactionsCount); - if (ec) { - makeErrorResponse(ec, resp); - return; - } - - serialize(txResp, outputSerializer); - } else if (method == "get_transfers_count") { - GetTransfersCountResponse trResp; - - std::error_code ec = service.getTransfersCount(trResp.transfersCount); - if (ec) { - makeErrorResponse(ec, resp); - return; - } - - serialize(trResp, outputSerializer); - } else if (method == "get_transaction_id_by_transfer_id") { - GetTransactionIdByTransferIdRequest getReq; - GetTransactionIdByTransferIdResponse getResp; - - //XXX: refactor it when migrate to different exception types in different subsystems! - try { - CryptoNote::JsonInputValueSerializer inputSerializer(req("params")); - serialize(getReq, inputSerializer); - } catch (std::exception&) { - makeGenericErrorReponse(resp, "Invalid Request", -32600); - return; - } - - size_t txId; - std::error_code ec = service.getTransactionByTransferId(getReq.transferId, txId); - getResp.transactionid = txId; - if (ec) { - makeErrorResponse(ec, resp); - return; - } - - serialize(getResp, outputSerializer); - } else if (method == "get_transaction") { - GetTransactionRequest getReq; - GetTransactionResponse getResp; - - //XXX: refactor it when migrate to different exception types in different subsystems! - try { - CryptoNote::JsonInputValueSerializer inputSerializer(req("params")); - serialize(getReq, inputSerializer); - } catch (std::exception&) { - makeGenericErrorReponse(resp, "Invalid Request", -32600); - return; - } - - std::error_code ec = service.getTransaction(getReq.transactionId, getResp.found, getResp.transactionInfo); - if (ec) { - makeErrorResponse(ec, resp); - return; - } - - serialize(getResp, outputSerializer); - } else if (method == "list_transactions") { - ListTransactionsRequest listReq; - ListTransactionsResponse listResp; - - //XXX: refactor it when migrate to different exception types in different subsystems! - try { - CryptoNote::JsonInputValueSerializer inputSerializer(req("params")); - serialize(listReq, inputSerializer); - } catch (std::exception&) { - makeGenericErrorReponse(resp, "Invalid Request", -32600); - return; - } - - std::error_code ec = service.listTransactions(static_cast(listReq.startingTransactionId), listReq.maxTransactionCount, listResp.transactions); - if (ec) { - makeErrorResponse(ec, resp); - return; - } - - serialize(listResp, outputSerializer); - } else if (method == "get_transfer") { - GetTransferRequest getReq; - GetTransferResponse getResp; - - //XXX: refactor it when migrate to different exception types in different subsystems! - try { - CryptoNote::JsonInputValueSerializer inputSerializer(req("params")); - serialize(getReq, inputSerializer); - } catch (std::exception&) { - makeGenericErrorReponse(resp, "Invalid Request", -32600); - return; - } - - std::error_code ec = service.getTransfer(getReq.transferId, getResp.found, getResp.transferInfo); - if (ec) { - makeErrorResponse(ec, resp); - return; - } - - serialize(getResp, outputSerializer); - } else if (method == "get_incoming_payments") { - GetIncomingPaymentsRequest getReq; - GetIncomingPaymentsResponse getResp; - - //XXX: refactor it when migrate to different exception types in different subsystems! - try { - CryptoNote::JsonInputValueSerializer inputSerializer(req("params")); - serialize(getReq, inputSerializer); - } catch (std::exception&) { - makeGenericErrorReponse(resp, "Invalid Request", -32600); - return; - } - - WalletService::IncomingPayments payments; - std::error_code ec = service.getIncomingPayments(getReq.payments, payments); - if (ec) { - if (ec == make_error_code(std::errc::argument_out_of_domain)) { - makeGenericErrorReponse(resp, "Invalid Request", -32600); - } else { - makeErrorResponse(ec, resp); - } - - return; - } - - for (auto p: payments) { - PaymentsById pbid; - pbid.id = std::move(p.first); - pbid.payments = std::move(p.second); - - getResp.payments.push_back(std::move(pbid)); - } - - serialize(getResp, outputSerializer); - } else { - logger(Logging::DEBUGGING) << "Requested method not found: " << method; + auto it = handlers.find(method); + if (it == handlers.end()) { + logger(Logging::WARNING) << "Requested method not found: " << method; makeMethodNotFoundResponse(resp); return; } - fillJsonResponse(outputSerializer.getValue(), resp); + logger(Logging::DEBUGGING) << method << " request came"; - } catch (RequestSerializationError&) { - logger(Logging::WARNING) << "Wrong request came"; - makeGenericErrorReponse(resp, "Invalid Request", -32600); + Common::JsonValue params(Common::JsonValue::OBJECT); + if (req.contains("params")) { + params = req("params"); + } + + it->second(params, resp); } catch (std::exception& e) { logger(Logging::WARNING) << "Error occurred while processing JsonRpc request: " << e.what(); makeGenericErrorReponse(resp, e.what()); } } +std::error_code PaymentServiceJsonRpcServer::handleReset(const Reset::Request& request, Reset::Response& response) { + if (request.viewSecretKey.empty()) { + return service.resetWallet(); + } else { + return service.replaceWithNewWallet(request.viewSecretKey); + } +} + +std::error_code PaymentServiceJsonRpcServer::handleCreateAddress(const CreateAddress::Request& request, CreateAddress::Response& response) { + if (request.spendSecretKey.empty() && request.spendPublicKey.empty()) { + return service.createAddress(response.address); + } else if (!request.spendSecretKey.empty()) { + return service.createAddress(request.spendSecretKey, response.address); + } else { + return service.createTrackingAddress(request.spendPublicKey, response.address); + } +} + +std::error_code PaymentServiceJsonRpcServer::handleDeleteAddress(const DeleteAddress::Request& request, DeleteAddress::Response& response) { + return service.deleteAddress(request.address); +} + +std::error_code PaymentServiceJsonRpcServer::handleGetSpendKeys(const GetSpendKeys::Request& request, GetSpendKeys::Response& response) { + return service.getSpendkeys(request.address, response.spendPublicKey, response.spendSecretKey); +} + +std::error_code PaymentServiceJsonRpcServer::handleGetBalance(const GetBalance::Request& request, GetBalance::Response& response) { + if (!request.address.empty()) { + return service.getBalance(request.address, response.availableBalance, response.lockedAmount); + } else { + return service.getBalance(response.availableBalance, response.lockedAmount); + } +} + +std::error_code PaymentServiceJsonRpcServer::handleGetBlockHashes(const GetBlockHashes::Request& request, GetBlockHashes::Response& response) { + return service.getBlockHashes(request.firstBlockIndex, request.blockCount, response.blockHashes); +} + +std::error_code PaymentServiceJsonRpcServer::handleGetTransactionHashes(const GetTransactionHashes::Request& request, GetTransactionHashes::Response& response) { + if (!request.blockHash.empty()) { + return service.getTransactionHashes(request.addresses, request.blockHash, request.blockCount, request.paymentId, response.items); + } else { + return service.getTransactionHashes(request.addresses, request.firstBlockIndex, request.blockCount, request.paymentId, response.items); + } +} + +std::error_code PaymentServiceJsonRpcServer::handleGetTransactions(const GetTransactions::Request& request, GetTransactions::Response& response) { + if (!request.blockHash.empty()) { + return service.getTransactions(request.addresses, request.blockHash, request.blockCount, request.paymentId, response.items); + } else { + return service.getTransactions(request.addresses, request.firstBlockIndex, request.blockCount, request.paymentId, response.items); + } +} + +std::error_code PaymentServiceJsonRpcServer::handleGetUnconfirmedTransactionHashes(const GetUnconfirmedTransactionHashes::Request& request, GetUnconfirmedTransactionHashes::Response& response) { + return service.getUnconfirmedTransactionHashes(request.addresses, response.transactionHashes); +} + +std::error_code PaymentServiceJsonRpcServer::handleGetTransaction(const GetTransaction::Request& request, GetTransaction::Response& response) { + return service.getTransaction(request.transactionHash, response.transaction); +} + +std::error_code PaymentServiceJsonRpcServer::handleSendTransaction(const SendTransaction::Request& request, SendTransaction::Response& response) { + return service.sendTransaction(request, response.transactionHash); +} + +std::error_code PaymentServiceJsonRpcServer::handleCreateDelayedTransaction(const CreateDelayedTransaction::Request& request, CreateDelayedTransaction::Response& response) { + return service.createDelayedTransaction(request, response.transactionHash); +} + +std::error_code PaymentServiceJsonRpcServer::handleGetDelayedTransactionHashes(const GetDelayedTransactionHashes::Request& request, GetDelayedTransactionHashes::Response& response) { + return service.getDelayedTransactionHashes(response.transactionHashes); +} + +std::error_code PaymentServiceJsonRpcServer::handleDeleteDelayedTransaction(const DeleteDelayedTransaction::Request& request, DeleteDelayedTransaction::Response& response) { + return service.deleteDelayedTransaction(request.transactionHash); +} + +std::error_code PaymentServiceJsonRpcServer::handleSendDelayedTransaction(const SendDelayedTransaction::Request& request, SendDelayedTransaction::Response& response) { + return service.sendDelayedTransaction(request.transactionHash); +} + +std::error_code PaymentServiceJsonRpcServer::handleGetViewKey(const GetViewKey::Request& request, GetViewKey::Response& response) { + return service.getViewKey(response.viewSecretKey); +} + +std::error_code PaymentServiceJsonRpcServer::handleGetStatus(const GetStatus::Request& request, GetStatus::Response& response) { + return service.getStatus(response.blockCount, response.knownBlockCount, response.lastBlockHash, response.peerCount); +} + +std::error_code PaymentServiceJsonRpcServer::handleGetAddresses(const GetAddresses::Request& request, GetAddresses::Response& response) { + return service.getAddresses(response.addresses); +} + } diff --git a/src/PaymentGate/PaymentServiceJsonRpcServer.h b/src/PaymentGate/PaymentServiceJsonRpcServer.h index 8e90b8d8..46621220 100644 --- a/src/PaymentGate/PaymentServiceJsonRpcServer.h +++ b/src/PaymentGate/PaymentServiceJsonRpcServer.h @@ -17,7 +17,13 @@ #pragma once +#include + +#include "Common/JsonValue.h" #include "JsonRpcServer/JsonRpcServer.h" +#include "PaymentServiceJsonRpcMessages.h" +#include "Serialization/JsonInputValueSerializer.h" +#include "Serialization/JsonOutputStreamSerializer.h" namespace PaymentService { @@ -34,6 +40,56 @@ protected: private: WalletService& service; Logging::LoggerRef logger; + + typedef std::function HandlerFunction; + + template + HandlerFunction jsonHandler(RequestHandler handler) { + return [handler] (const Common::JsonValue& jsonRpcParams, Common::JsonValue& jsonResponse) mutable { + RequestType request; + ResponseType response; + + try { + CryptoNote::JsonInputValueSerializer inputSerializer(const_cast(jsonRpcParams)); + serialize(request, inputSerializer); + } catch (std::exception&) { + makeGenericErrorReponse(jsonResponse, "Invalid Request", -32600); + return; + } + + std::error_code ec = handler(request, response); + if (ec) { + makeErrorResponse(ec, jsonResponse); + return; + } + + CryptoNote::JsonOutputStreamSerializer outputSerializer; + serialize(response, outputSerializer); + fillJsonResponse(outputSerializer.getValue(), jsonResponse); + }; + } + + std::unordered_map handlers; + + std::error_code handleReset(const Reset::Request& request, Reset::Response& response); + std::error_code handleCreateAddress(const CreateAddress::Request& request, CreateAddress::Response& response); + std::error_code handleDeleteAddress(const DeleteAddress::Request& request, DeleteAddress::Response& response); + std::error_code handleGetSpendKeys(const GetSpendKeys::Request& request, GetSpendKeys::Response& response); + std::error_code handleGetBalance(const GetBalance::Request& request, GetBalance::Response& response); + std::error_code handleGetBlockHashes(const GetBlockHashes::Request& request, GetBlockHashes::Response& response); + std::error_code handleGetTransactionHashes(const GetTransactionHashes::Request& request, GetTransactionHashes::Response& response); + std::error_code handleGetTransactions(const GetTransactions::Request& request, GetTransactions::Response& response); + std::error_code handleGetUnconfirmedTransactionHashes(const GetUnconfirmedTransactionHashes::Request& request, GetUnconfirmedTransactionHashes::Response& response); + std::error_code handleGetTransaction(const GetTransaction::Request& request, GetTransaction::Response& response); + std::error_code handleSendTransaction(const SendTransaction::Request& request, SendTransaction::Response& response); + std::error_code handleCreateDelayedTransaction(const CreateDelayedTransaction::Request& request, CreateDelayedTransaction::Response& response); + std::error_code handleGetDelayedTransactionHashes(const GetDelayedTransactionHashes::Request& request, GetDelayedTransactionHashes::Response& response); + std::error_code handleDeleteDelayedTransaction(const DeleteDelayedTransaction::Request& request, DeleteDelayedTransaction::Response& response); + std::error_code handleSendDelayedTransaction(const SendDelayedTransaction::Request& request, SendDelayedTransaction::Response& response); + std::error_code handleGetViewKey(const GetViewKey::Request& request, GetViewKey::Response& response); + std::error_code handleGetStatus(const GetStatus::Request& request, GetStatus::Response& response); + std::error_code handleGetAddresses(const GetAddresses::Request& request, GetAddresses::Response& response); + }; -} //namespace PaymentService +}//namespace PaymentService diff --git a/src/PaymentGate/WalletService.cpp b/src/PaymentGate/WalletService.cpp index ad1202b6..ae1b3211 100755 --- a/src/PaymentGate/WalletService.cpp +++ b/src/PaymentGate/WalletService.cpp @@ -30,27 +30,26 @@ #include "Common/Util.h" #include "crypto/crypto.h" +#include "CryptoNote.h" #include "CryptoNoteCore/CryptoNoteFormatUtils.h" #include "CryptoNoteCore/CryptoNoteBasicImpl.h" #include "CryptoNoteCore/TransactionExtra.h" +#include + #include "PaymentServiceJsonRpcMessages.h" #include "WalletFactory.h" #include "NodeFactory.h" #include "Wallet/LegacyKeysImporter.h" +#include "Wallet/WalletErrors.h" +#include "Wallet/WalletUtils.h" +#include "WalletServiceErrorCategory.h" + +namespace PaymentService { namespace { -void addPaymentIdToExtra(const std::string& paymentId, std::string& extra) { - std::vector extraVector; - if (!CryptoNote::createTxExtraWithPaymentId(paymentId, extraVector)) { - throw std::runtime_error("Couldn't add payment id to extra"); - } - - std::copy(extraVector.begin(), extraVector.end(), std::back_inserter(extra)); -} - bool checkPaymentId(const std::string& paymentId) { if (paymentId.size() != 64) { return false; @@ -73,6 +72,96 @@ bool checkPaymentId(const std::string& paymentId) { }); } +Crypto::Hash parsePaymentId(const std::string& paymentIdStr) { + if (!checkPaymentId(paymentIdStr)) { + throw std::system_error(make_error_code(CryptoNote::error::WalletServiceErrorCode::WRONG_PAYMENT_ID_FORMAT)); + } + + Crypto::Hash paymentId; + bool r = Common::podFromHex(paymentIdStr, paymentId); + assert(r); + + return paymentId; +} + +bool getPaymentIdFromExtra(const std::string& binaryString, Crypto::Hash& paymentId) { + return CryptoNote::getPaymentIdFromTxExtra(Common::asBinaryArray(binaryString), paymentId); +} + +std::string getPaymentIdStringFromExtra(const std::string& binaryString) { + Crypto::Hash paymentId; + + if (!getPaymentIdFromExtra(binaryString, paymentId)) { + return std::string(); + } + + return Common::podToHex(paymentId); +} + +} + +struct TransactionsInBlockInfoFilter { + TransactionsInBlockInfoFilter(const std::vector& addressesVec, const std::string& paymentIdStr) { + addresses.insert(addressesVec.begin(), addressesVec.end()); + + if (!paymentIdStr.empty()) { + paymentId = parsePaymentId(paymentIdStr); + havePaymentId = true; + } else { + havePaymentId = false; + } + } + + bool checkTransaction(const CryptoNote::WalletTransactionWithTransfers& transaction) const { + if (havePaymentId) { + Crypto::Hash transactionPaymentId; + if (!getPaymentIdFromExtra(transaction.transaction.extra, transactionPaymentId)) { + return false; + } + + if (paymentId != transactionPaymentId) { + return false; + } + } + + if (addresses.empty()) { + return true; + } + + bool haveAddress = false; + for (const CryptoNote::WalletTransfer& transfer: transaction.transfers) { + if (addresses.find(transfer.address) != addresses.end()) { + haveAddress = true; + break; + } + } + + return haveAddress; + } + + std::unordered_set addresses; + bool havePaymentId = false; + Crypto::Hash paymentId; +}; + +namespace { + +void addPaymentIdToExtra(const std::string& paymentId, std::string& extra) { + std::vector extraVector; + if (!CryptoNote::createTxExtraWithPaymentId(paymentId, extraVector)) { + throw std::runtime_error("Couldn't add payment id to extra"); + } + + std::copy(extraVector.begin(), extraVector.end(), std::back_inserter(extra)); +} + +void validatePaymentId(const std::string& paymentId, Logging::LoggerRef logger) { + if (!checkPaymentId(paymentId)) { + logger(Logging::WARNING) << "Can't validate payment id: " << paymentId; + throw std::system_error(make_error_code(CryptoNote::error::WalletServiceErrorCode::WRONG_PAYMENT_ID_FORMAT)); + } +} + bool createOutputBinaryFile(const std::string& filename, std::fstream& file) { file.open(filename.c_str(), std::fstream::in | std::fstream::out | std::ofstream::binary); if (file) { @@ -114,11 +203,146 @@ void replaceWalletFiles(const std::string &path, const std::string &tempFilePath Tools::replace_file(tempFilePath, path); } +Crypto::Hash parseHash(const std::string& hashString, Logging::LoggerRef logger) { + Crypto::Hash hash; + + if (!Common::podFromHex(hashString, hash)) { + logger(Logging::WARNING) << "Can't parse hash string " << hashString; + throw std::system_error(make_error_code(CryptoNote::error::WalletServiceErrorCode::WRONG_HASH_FORMAT)); + } + + return hash; } -namespace PaymentService { +std::vector filterTransactions( + const std::vector& blocks, + const TransactionsInBlockInfoFilter& filter) { + + std::vector result; + + for (const auto& block: blocks) { + CryptoNote::TransactionsInBlockInfo item; + item.blockHash = block.blockHash; + + for (const auto& transaction: block.transactions) { + if (transaction.transaction.state != CryptoNote::WalletTransactionState::DELETED && filter.checkTransaction(transaction)) { + item.transactions.push_back(transaction); + } + } + + result.push_back(std::move(item)); + } + + return result; +} + +PaymentService::TransactionRpcInfo convertTransactionWithTransfersToTransactionRpcInfo( + const CryptoNote::WalletTransactionWithTransfers& transactionWithTransfers) { + + PaymentService::TransactionRpcInfo transactionInfo; + + transactionInfo.state = static_cast(transactionWithTransfers.transaction.state); + transactionInfo.transactionHash = Common::podToHex(transactionWithTransfers.transaction.hash); + transactionInfo.blockIndex = transactionWithTransfers.transaction.blockHeight; + transactionInfo.timestamp = transactionWithTransfers.transaction.timestamp; + transactionInfo.isBase = transactionWithTransfers.transaction.isBase; + transactionInfo.unlockTime = transactionWithTransfers.transaction.unlockTime; + transactionInfo.amount = transactionWithTransfers.transaction.totalAmount; + transactionInfo.fee = transactionWithTransfers.transaction.fee; + transactionInfo.extra = Common::toHex(transactionWithTransfers.transaction.extra.data(), transactionWithTransfers.transaction.extra.size()); + transactionInfo.paymentId = getPaymentIdStringFromExtra(transactionWithTransfers.transaction.extra); + + for (const CryptoNote::WalletTransfer& transfer: transactionWithTransfers.transfers) { + PaymentService::TransferRpcInfo rpcTransfer; + rpcTransfer.address = transfer.address; + rpcTransfer.amount = transfer.amount; + rpcTransfer.type = static_cast(transfer.type); + + transactionInfo.transfers.push_back(std::move(rpcTransfer)); + } + + return transactionInfo; +} + +std::vector convertTransactionsInBlockInfoToTransactionsInBlockRpcInfo( + const std::vector& blocks) { + + std::vector rpcBlocks; + rpcBlocks.reserve(blocks.size()); + for (const auto& block: blocks) { + PaymentService::TransactionsInBlockRpcInfo rpcBlock; + rpcBlock.blockHash = Common::podToHex(block.blockHash); + + for (const CryptoNote::WalletTransactionWithTransfers& transactionWithTransfers: block.transactions) { + PaymentService::TransactionRpcInfo transactionInfo = convertTransactionWithTransfersToTransactionRpcInfo(transactionWithTransfers); + rpcBlock.transactions.push_back(std::move(transactionInfo)); + } + + rpcBlocks.push_back(std::move(rpcBlock)); + } + + return rpcBlocks; +} + +std::vector convertTransactionsInBlockInfoToTransactionHashesInBlockRpcInfo( + const std::vector& blocks) { + + std::vector transactionHashes; + transactionHashes.reserve(blocks.size()); + for (const CryptoNote::TransactionsInBlockInfo& block: blocks) { + PaymentService::TransactionHashesInBlockRpcInfo item; + item.blockHash = Common::podToHex(block.blockHash); + + for (const CryptoNote::WalletTransactionWithTransfers& transaction: block.transactions) { + item.transactionHashes.emplace_back(Common::podToHex(transaction.transaction.hash)); + } + + transactionHashes.push_back(std::move(item)); + } + + return transactionHashes; +} + +void validateAddresses(const std::vector& addresses, const CryptoNote::Currency& currency, Logging::LoggerRef logger) { + for (const auto& address: addresses) { + if (!CryptoNote::validateAddress(address, currency)) { + logger(Logging::WARNING) << "Can't validate address " << address; + throw std::system_error(make_error_code(CryptoNote::error::BAD_ADDRESS)); + } + } +} + +std::vector collectDestinationAddresses(const std::vector& orders) { + std::vector result; + + result.reserve(orders.size()); + for (const auto& order: orders) { + result.push_back(order.address); + } + + return result; +} + +std::vector convertWalletRpcOrdersToWalletOrders(const std::vector& orders) { + std::vector result; + result.reserve(orders.size()); + + for (const auto& order: orders) { + result.emplace_back(CryptoNote::WalletOrder {order.address, order.amount}); + } + + return result; +} + +} void createWalletFile(std::fstream& walletFile, const std::string& filename) { + boost::filesystem::path pathToWalletFile(filename); + boost::filesystem::path directory = pathToWalletFile.parent_path(); + if (!directory.empty() && !Tools::directoryExists(directory.string())) { + throw std::runtime_error("Directory does not exist: " + directory.string()); + } + walletFile.open(filename.c_str(), std::fstream::in | std::fstream::out | std::fstream::binary); if (walletFile) { walletFile.close(); @@ -131,12 +355,12 @@ void createWalletFile(std::fstream& walletFile, const std::string& filename) { walletFile.open(filename.c_str(), std::fstream::in | std::fstream::out | std::fstream::binary); } -void saveWallet(CryptoNote::IWallet* wallet, std::fstream& walletFile, bool saveDetailed = true, bool saveCache = true) { - wallet->save(walletFile, saveDetailed, saveCache); +void saveWallet(CryptoNote::IWallet& wallet, std::fstream& walletFile, bool saveDetailed = true, bool saveCache = true) { + wallet.save(walletFile, saveDetailed, saveCache); walletFile.flush(); } -void secureSaveWallet(CryptoNote::IWallet* wallet, const std::string& path, bool saveDetailed = true, bool saveCache = true) { +void secureSaveWallet(CryptoNote::IWallet& wallet, const std::string& path, bool saveDetailed = true, bool saveCache = true) { std::fstream tempFile; std::string tempFilePath = createTemporaryFile(path, tempFile); @@ -171,7 +395,7 @@ void generateNewWallet(const CryptoNote::Currency ¤cy, const WalletConfigu log(Logging::INFO) << "New wallet is generated. Address: " << address; - saveWallet(wallet, walletFile, false, false); + saveWallet(*wallet, walletFile, false, false); log(Logging::INFO) << "Wallet is saved"; } @@ -189,38 +413,39 @@ void importLegacyKeys(const std::string &legacyKeysFile, const WalletConfigurati } WalletService::WalletService(const CryptoNote::Currency& currency, System::Dispatcher& sys, CryptoNote::INode& node, - const WalletConfiguration& conf, Logging::ILogger& logger) : + CryptoNote::IWallet& wallet, const WalletConfiguration& conf, Logging::ILogger& logger) : + currency(currency), + wallet(wallet), + node(node), config(conf), inited(false), logger(logger, "WalletService"), - txIdIndex(boost::get<0>(paymentsCache)), - paymentIdIndex(boost::get<1>(paymentsCache)), dispatcher(sys), + readyEvent(dispatcher), refreshContext(dispatcher) { - wallet.reset(WalletFactory::createWallet(currency, node, dispatcher)); + readyEvent.set(); } WalletService::~WalletService() { - if (wallet) { - if (inited) { - wallet->stop(); - refreshContext.wait(); - wallet->shutdown(); - } + if (inited) { + wallet.stop(); + refreshContext.wait(); + wallet.shutdown(); } } void WalletService::init() { loadWallet(); - loadPaymentsCacheAndTransferIndices(); + loadTransactionIdIndex(); + refreshContext.spawn([this] { refresh(); }); inited = true; } void WalletService::saveWallet() { - PaymentService::secureSaveWallet(wallet.get(), config.walletFile, true, true); + PaymentService::secureSaveWallet(wallet, config.walletFile, true, true); logger(Logging::INFO) << "Wallet is saved"; } @@ -233,340 +458,563 @@ void WalletService::loadWallet() { logger(Logging::INFO) << "Loading wallet"; - wallet->load(inputWalletFile, config.walletPassword); + wallet.load(inputWalletFile, config.walletPassword); logger(Logging::INFO) << "Wallet loading is finished."; } -void WalletService::loadPaymentsCacheAndTransferIndices() { - size_t txCount = wallet->getTransactionCount(); - transfersIndices.reserve(txCount + 1); - transfersIndices.push_back(0); +void WalletService::loadTransactionIdIndex() { + transactionIdIndex.clear(); - logger(Logging::DEBUGGING) << "seeking for payments among " << txCount << " transactions"; - - for (size_t id = 0; id < txCount; ++id) { - CryptoNote::WalletTransaction tx = wallet->getTransaction(id); - - transfersIndices.push_back(transfersIndices[id] + wallet->getTransactionTransferCount(id)); - - if (tx.totalAmount < 0) { - logger(Logging::DEBUGGING) << "tx " << id << " has negative amount"; - continue; - } - - std::vector extraVector(tx.extra.begin(), tx.extra.end()); - - Crypto::Hash paymentId; - if (!CryptoNote::getPaymentIdFromTxExtra(extraVector, paymentId)) { - logger(Logging::DEBUGGING) << "tx " << id << " has no payment id"; - continue; - } - - logger(Logging::DEBUGGING) << "transaction " << id << " has been inserted with payment id " << paymentId; - insertTransaction(id, paymentId, tx.blockHeight != CryptoNote::WALLET_UNCONFIRMED_TRANSACTION_HEIGHT); + for (size_t i = 0; i < wallet.getTransactionCount(); ++i) { + transactionIdIndex.emplace(Common::podToHex(wallet.getTransaction(i).hash), i); } } -std::error_code WalletService::sendTransaction(const SendTransactionRequest& req, SendTransactionResponse& resp) { - assert(wallet); - logger(Logging::DEBUGGING) << "Send transaction request came"; - +std::error_code WalletService::resetWallet() { try { - std::vector orders; - makeOrders(req.destinations, orders); + System::EventLock lk(readyEvent); - std::string extra; - if (!req.paymentId.empty()) { - addPaymentIdToExtra(req.paymentId, extra); + logger(Logging::INFO) << "Reseting wallet"; + + if (!inited) { + logger(Logging::WARNING) << "Reset impossible: Wallet Service is not initialized"; + return make_error_code(CryptoNote::error::NOT_INITIALIZED); } - size_t txId = wallet->transfer(orders, req.fee, req.mixin, extra, req.unlockTime); - if (txId == CryptoNote::WALLET_INVALID_TRANSACTION_ID) { - logger(Logging::WARNING) << "Unable to send transaction"; - throw std::runtime_error("Error occurred while sending transaction"); - } - - resp.transactionId = txId; + reset(); + logger(Logging::INFO) << "Wallet has been reset"; } catch (std::system_error& x) { - logger(Logging::WARNING) << "Error while sending transaction: " << x.what(); + logger(Logging::WARNING) << "Error while reseting wallet: " << x.what(); return x.code(); + } catch (std::exception& x) { + logger(Logging::WARNING) << "Error while reseting wallet: " << x.what(); + return make_error_code(CryptoNote::error::INTERNAL_WALLET_ERROR); } return std::error_code(); } -void WalletService::makeOrders(const std::vector& destinations, std::vector& orders) { - orders.reserve(destinations.size()); - - for (auto dest: destinations) { - orders.push_back( { dest.address, dest.amount } ); - } -} - -std::error_code WalletService::getAddress(size_t index, std::string& address) { - logger(Logging::DEBUGGING) << "Get address request came"; - +std::error_code WalletService::replaceWithNewWallet(const std::string& viewSecretKeyText) { try { - address = wallet->getAddress(index); + System::EventLock lk(readyEvent); + + Crypto::SecretKey viewSecretKey; + if (!Common::podFromHex(viewSecretKeyText, viewSecretKey)) { + logger(Logging::WARNING) << "Cannot restore view secret key: " << viewSecretKeyText; + return make_error_code(CryptoNote::error::WalletServiceErrorCode::WRONG_KEY_FORMAT); + } + + Crypto::PublicKey viewPublicKey; + if (!Crypto::secret_key_to_public_key(viewSecretKey, viewPublicKey)) { + logger(Logging::WARNING) << "Cannot derive view public key, wrong secret key: " << viewSecretKeyText; + return make_error_code(CryptoNote::error::WalletServiceErrorCode::WRONG_KEY_FORMAT); + } + + replaceWithNewWallet(viewSecretKey); + logger(Logging::INFO) << "The container has been replaced"; } catch (std::system_error& x) { - logger(Logging::WARNING) << "Error while getting address: " << x.what(); + logger(Logging::WARNING) << "Error while replacing container: " << x.what(); return x.code(); + } catch (std::exception& x) { + logger(Logging::WARNING) << "Error while replacing container: " << x.what(); + return make_error_code(CryptoNote::error::INTERNAL_WALLET_ERROR); } return std::error_code(); } -std::error_code WalletService::getAddressCount(std::size_t& count) { - logger(Logging::DEBUGGING) << "Get address count request came"; - count = wallet->getAddressCount(); - return std::error_code(); -} - -std::error_code WalletService::createAddress(std::string& address) { - logger(Logging::DEBUGGING) << "Create address request came"; - +std::error_code WalletService::createAddress(const std::string& spendSecretKeyText, std::string& address) { try { - address = wallet->createAddress(); + System::EventLock lk(readyEvent); + + logger(Logging::DEBUGGING) << "Creating address"; + + Crypto::SecretKey secretKey; + if (!Common::podFromHex(spendSecretKeyText, secretKey)) { + logger(Logging::WARNING) << "Wrong key format: " << spendSecretKeyText; + return make_error_code(CryptoNote::error::WalletServiceErrorCode::WRONG_KEY_FORMAT); + } + + address = wallet.createAddress(secretKey); } catch (std::system_error& x) { logger(Logging::WARNING) << "Error while creating address: " << x.what(); return x.code(); } + logger(Logging::DEBUGGING) << "Created address " << address; + + return std::error_code(); +} + +std::error_code WalletService::createAddress(std::string& address) { + try { + System::EventLock lk(readyEvent); + + logger(Logging::DEBUGGING) << "Creating address"; + + address = wallet.createAddress(); + } catch (std::system_error& x) { + logger(Logging::WARNING) << "Error while creating address: " << x.what(); + return x.code(); + } + + logger(Logging::DEBUGGING) << "Created address " << address; + + return std::error_code(); +} + +std::error_code WalletService::createTrackingAddress(const std::string& spendPublicKeyText, std::string& address) { + try { + System::EventLock lk(readyEvent); + + logger(Logging::DEBUGGING) << "Creating tracking address"; + + Crypto::PublicKey publicKey; + if (!Common::podFromHex(spendPublicKeyText, publicKey)) { + logger(Logging::WARNING) << "Wrong key format: " << spendPublicKeyText; + return make_error_code(CryptoNote::error::WalletServiceErrorCode::WRONG_KEY_FORMAT); + } + + address = wallet.createAddress(publicKey); + } catch (std::system_error& x) { + logger(Logging::WARNING) << "Error while creating tracking address: " << x.what(); + return x.code(); + } + + logger(Logging::DEBUGGING) << "Created address " << address; return std::error_code(); } std::error_code WalletService::deleteAddress(const std::string& address) { - logger(Logging::DEBUGGING) << "Delete address request came"; - try { - wallet->deleteAddress(address); + System::EventLock lk(readyEvent); + + logger(Logging::DEBUGGING) << "Delete address request came"; + wallet.deleteAddress(address); } catch (std::system_error& x) { logger(Logging::WARNING) << "Error while deleting address: " << x.what(); return x.code(); } + logger(Logging::DEBUGGING) << "Address " << address << " successfully deleted"; return std::error_code(); } -std::error_code WalletService::getActualBalance(const std::string& address, uint64_t& actualBalance) { - logger(Logging::DEBUGGING) << "Get actual balance for address: " << address << " request came"; - +std::error_code WalletService::getSpendkeys(const std::string& address, std::string& publicSpendKeyText, std::string& secretSpendKeyText) { try { - actualBalance = wallet->getActualBalance(address); + System::EventLock lk(readyEvent); + + CryptoNote::KeyPair key = wallet.getAddressSpendKey(address); + + publicSpendKeyText = Common::podToHex(key.publicKey); + secretSpendKeyText = Common::podToHex(key.secretKey); + } catch (std::system_error& x) { - logger(Logging::WARNING) << "Unable to get actual balance: " << x.what(); + logger(Logging::WARNING) << "Error while getting spend key: " << x.what(); return x.code(); } return std::error_code(); } -std::error_code WalletService::getPendingBalance(const std::string& address, uint64_t& pendingBalance) { - logger(Logging::DEBUGGING) << "Get pending balance for address: " << address <<" request came"; - +std::error_code WalletService::getBalance(const std::string& address, uint64_t& availableBalance, uint64_t& lockedAmount) { try { - pendingBalance = wallet->getPendingBalance(address); + System::EventLock lk(readyEvent); + logger(Logging::DEBUGGING) << "Getting balance for address " << address; + + availableBalance = wallet.getActualBalance(address); + lockedAmount = wallet.getPendingBalance(address); } catch (std::system_error& x) { - logger(Logging::WARNING) << "Unable to get pending balance: " << x.what(); + logger(Logging::WARNING) << "Error while getting balance: " << x.what(); + return x.code(); + } + + logger(Logging::DEBUGGING) << address << " actual balance: " << availableBalance << ", pending: " << lockedAmount; + return std::error_code(); +} + +std::error_code WalletService::getBalance(uint64_t& availableBalance, uint64_t& lockedAmount) { + try { + System::EventLock lk(readyEvent); + logger(Logging::DEBUGGING) << "Getting wallet balance"; + + availableBalance = wallet.getActualBalance(); + lockedAmount = wallet.getPendingBalance(); + } catch (std::system_error& x) { + logger(Logging::WARNING) << "Error while getting balance: " << x.what(); + return x.code(); + } + + logger(Logging::DEBUGGING) << "Wallet actual balance: " << availableBalance << ", pending: " << lockedAmount; + return std::error_code(); +} + +std::error_code WalletService::getBlockHashes(uint32_t firstBlockIndex, uint32_t blockCount, std::vector& blockHashes) { + try { + System::EventLock lk(readyEvent); + std::vector hashes = wallet.getBlockHashes(firstBlockIndex, blockCount); + + blockHashes.reserve(hashes.size()); + for (const auto& hash: hashes) { + blockHashes.push_back(Common::podToHex(hash)); + } + } catch (std::system_error& x) { + logger(Logging::WARNING) << "Error while getting block hashes: " << x.what(); return x.code(); } return std::error_code(); } -std::error_code WalletService::getActualBalance(uint64_t& actualBalance) { - logger(Logging::DEBUGGING) << "Get actual balance request came"; - +std::error_code WalletService::getViewKey(std::string& viewSecretKey) { try { - actualBalance = wallet->getActualBalance(); + System::EventLock lk(readyEvent); + CryptoNote::KeyPair viewKey = wallet.getViewKey(); + viewSecretKey = Common::podToHex(viewKey.secretKey); } catch (std::system_error& x) { - logger(Logging::WARNING) << "Unable to get actual balance: " << x.what(); + logger(Logging::WARNING) << "Error while getting view key: " << x.what(); return x.code(); } return std::error_code(); } -std::error_code WalletService::getPendingBalance(uint64_t& pendingBalance) { - logger(Logging::DEBUGGING) << "Get pending balance request came"; - +std::error_code WalletService::getTransactionHashes(const std::vector& addresses, const std::string& blockHashString, + uint32_t blockCount, const std::string& paymentId, std::vector& transactionHashes) { try { - pendingBalance = wallet->getPendingBalance(); - } catch (std::system_error& x) { - logger(Logging::WARNING) << "Unable to get pending balance: " << x.what(); - return x.code(); - } + System::EventLock lk(readyEvent); + validateAddresses(addresses, currency, logger); - return std::error_code(); -} - -std::error_code WalletService::getTransactionsCount(uint64_t& txCount) { - logger(Logging::DEBUGGING) << "Get get transactions count request came"; - - try { - txCount = wallet->getTransactionCount(); - } catch (std::system_error& x) { - logger(Logging::WARNING) << "Unable to get transactions count: " << x.what(); - return x.code(); - } - - return std::error_code(); -} - -std::error_code WalletService::getTransfersCount(uint64_t& trCount) { - logger(Logging::DEBUGGING) << "Get transfers count request came"; - trCount = static_cast(transfersIndices.back()); - return std::error_code(); -} - -std::error_code WalletService::getTransactionByTransferId(size_t transferId, size_t& transactionId) { - logger(Logging::DEBUGGING) << "getTransactionByTransferId request came"; - - if (transferId >= transfersIndices.back()) { - logger(Logging::WARNING) << "Transfer ID:" << transferId<<" is out of domain"; - return std::make_error_code(std::errc::argument_out_of_domain); - } - - auto nextTxId = std::upper_bound(transfersIndices.begin(), transfersIndices.end(), transferId); - assert(nextTxId != transfersIndices.begin()); - transactionId = std::distance(transfersIndices.begin(), nextTxId) - 1; - - return std::error_code(); -} - -void WalletService::fillTransactionRpcInfo(size_t txId, const CryptoNote::WalletTransaction& tx, TransactionRpcInfo& rpcInfo) { - rpcInfo.firstTransferId = transfersIndices[txId]; - rpcInfo.transferCount = wallet->getTransactionTransferCount(txId); - rpcInfo.totalAmount = tx.totalAmount; - rpcInfo.fee = tx.fee; - rpcInfo.blockHeight = tx.blockHeight; - rpcInfo.timestamp = tx.timestamp; - rpcInfo.extra = Common::toHex(tx.extra.data(), tx.extra.size()); - rpcInfo.hash = Common::podToHex(tx.hash); - rpcInfo.state = static_cast(tx.state); - for (size_t transferId = 0; transferId < rpcInfo.transferCount; ++transferId) { - auto transfer = wallet->getTransactionTransfer(txId, transferId); - TransferRpcInfo rpcTransfer{ static_cast(transfer.type), transfer.address, transfer.amount }; - rpcInfo.transfers.push_back(rpcTransfer); - } -} - -std::error_code WalletService::getTransaction(size_t txId, bool& found, TransactionRpcInfo& rpcInfo) { - logger(Logging::DEBUGGING) << "getTransaction request came"; - - found = false; - - try { - if (txId + 1 >= transfersIndices.size()) { - logger(Logging::WARNING) << "Unable to get transaction " << txId << ": argument out of domain."; - return std::make_error_code(std::errc::argument_out_of_domain); + if (!paymentId.empty()) { + validatePaymentId(paymentId, logger); } - auto tx = wallet->getTransaction(txId); + TransactionsInBlockInfoFilter transactionFilter(addresses, paymentId); + Crypto::Hash blockHash = parseHash(blockHashString, logger); - fillTransactionRpcInfo(txId, tx, rpcInfo); - - found = true; + transactionHashes = getRpcTransactionHashes(blockHash, blockCount, transactionFilter); } catch (std::system_error& x) { - logger(Logging::WARNING) << "Unable to get transaction: " << x.what(); + logger(Logging::WARNING) << "Error while getting transactions: " << x.what(); return x.code(); + } catch (std::exception& x) { + logger(Logging::WARNING) << "Error while getting transactions: " << x.what(); + return make_error_code(CryptoNote::error::INTERNAL_WALLET_ERROR); } return std::error_code(); } -std::error_code WalletService::listTransactions(size_t startingTxId, uint32_t maxTxCount, std::vector& txsRpcInfo) { - logger(Logging::DEBUGGING) << "listTransactions request came"; +std::error_code WalletService::getTransactionHashes(const std::vector& addresses, uint32_t firstBlockIndex, + uint32_t blockCount, const std::string& paymentId, std::vector& transactionHashes) { + try { + System::EventLock lk(readyEvent); + validateAddresses(addresses, currency, logger); - if (maxTxCount == 0) { - txsRpcInfo.clear(); - return std::error_code(); + if (!paymentId.empty()) { + validatePaymentId(paymentId, logger); + } + + TransactionsInBlockInfoFilter transactionFilter(addresses, paymentId); + transactionHashes = getRpcTransactionHashes(firstBlockIndex, blockCount, transactionFilter); + + } catch (std::system_error& x) { + logger(Logging::WARNING) << "Error while getting transactions: " << x.what(); + return x.code(); + } catch (std::exception& x) { + logger(Logging::WARNING) << "Error while getting transactions: " << x.what(); + return make_error_code(CryptoNote::error::INTERNAL_WALLET_ERROR); } + return std::error_code(); +} + +std::error_code WalletService::getTransactions(const std::vector& addresses, const std::string& blockHashString, + uint32_t blockCount, const std::string& paymentId, std::vector& transactions) { try { - size_t endTxId; - if (startingTxId > std::numeric_limits::max() - static_cast(maxTxCount)) { - endTxId = static_cast(wallet->getTransactionCount()); + System::EventLock lk(readyEvent); + validateAddresses(addresses, currency, logger); + + if (!paymentId.empty()) { + validatePaymentId(paymentId, logger); + } + + TransactionsInBlockInfoFilter transactionFilter(addresses, paymentId); + + Crypto::Hash blockHash = parseHash(blockHashString, logger); + + transactions = getRpcTransactions(blockHash, blockCount, transactionFilter); + } catch (std::system_error& x) { + logger(Logging::WARNING) << "Error while getting transactions: " << x.what(); + return x.code(); + } catch (std::exception& x) { + logger(Logging::WARNING) << "Error while getting transactions: " << x.what(); + return make_error_code(CryptoNote::error::INTERNAL_WALLET_ERROR); + } + + return std::error_code(); +} + +std::error_code WalletService::getTransactions(const std::vector& addresses, uint32_t firstBlockIndex, + uint32_t blockCount, const std::string& paymentId, std::vector& transactions) { + try { + System::EventLock lk(readyEvent); + validateAddresses(addresses, currency, logger); + + if (!paymentId.empty()) { + validatePaymentId(paymentId, logger); + } + + TransactionsInBlockInfoFilter transactionFilter(addresses, paymentId); + + transactions = getRpcTransactions(firstBlockIndex, blockCount, transactionFilter); + } catch (std::system_error& x) { + logger(Logging::WARNING) << "Error while getting transactions: " << x.what(); + return x.code(); + } catch (std::exception& x) { + logger(Logging::WARNING) << "Error while getting transactions: " << x.what(); + return make_error_code(CryptoNote::error::INTERNAL_WALLET_ERROR); + } + + return std::error_code(); +} + +std::error_code WalletService::getTransaction(const std::string& transactionHash, TransactionRpcInfo& transaction) { + try { + System::EventLock lk(readyEvent); + Crypto::Hash hash = parseHash(transactionHash, logger); + + CryptoNote::WalletTransactionWithTransfers transactionWithTransfers = wallet.getTransaction(hash); + + if (transactionWithTransfers.transaction.state == CryptoNote::WalletTransactionState::DELETED) { + logger(Logging::WARNING) << "Transaction " << transactionHash << " is deleted"; + return make_error_code(CryptoNote::error::OBJECT_NOT_FOUND); + } + + transaction = convertTransactionWithTransfersToTransactionRpcInfo(transactionWithTransfers); + } catch (std::system_error& x) { + logger(Logging::WARNING) << "Error while getting transaction: " << x.what(); + return x.code(); + } catch (std::exception& x) { + logger(Logging::WARNING) << "Error while getting transaction: " << x.what(); + return make_error_code(CryptoNote::error::INTERNAL_WALLET_ERROR); + } + + return std::error_code(); +} + +std::error_code WalletService::getAddresses(std::vector& addresses) { + try { + System::EventLock lk(readyEvent); + + addresses.clear(); + addresses.reserve(wallet.getAddressCount()); + + for (size_t i = 0; i < wallet.getAddressCount(); ++i) { + addresses.push_back(wallet.getAddress(i)); + } + } catch (std::exception& e) { + logger(Logging::WARNING) << "Can't get addresses: " << e.what(); + return make_error_code(CryptoNote::error::INTERNAL_WALLET_ERROR); + } + + return std::error_code(); +} + +std::error_code WalletService::sendTransaction(const SendTransaction::Request& request, std::string& transactionHash) { + try { + System::EventLock lk(readyEvent); + + validateAddresses(request.sourceAddresses, currency, logger); + validateAddresses(collectDestinationAddresses(request.transfers), currency, logger); + if (!request.changeAddress.empty()) { + validateAddresses({ request.changeAddress }, currency, logger); + } + + CryptoNote::TransactionParameters sendParams; + if (!request.paymentId.empty()) { + addPaymentIdToExtra(request.paymentId, sendParams.extra); } else { - endTxId = startingTxId + static_cast(maxTxCount); - endTxId = std::min(endTxId, static_cast(wallet->getTransactionCount())); + sendParams.extra = Common::asString(Common::fromHex(request.extra)); } - txsRpcInfo.resize(endTxId - startingTxId); + sendParams.sourceAddresses = request.sourceAddresses; + sendParams.destinations = convertWalletRpcOrdersToWalletOrders(request.transfers); + sendParams.fee = request.fee; + sendParams.mixIn = request.anonymity; + sendParams.unlockTimestamp = request.unlockTime; + sendParams.changeDestination = request.changeAddress; - for (auto txId = startingTxId; txId < endTxId; ++txId) { - assert(txId < wallet->getTransactionCount()); - auto tx = wallet->getTransaction(txId); - fillTransactionRpcInfo(txId, tx, txsRpcInfo[txId - startingTxId]); - } + size_t transactionId = wallet.transfer(sendParams); + transactionHash = Common::podToHex(wallet.getTransaction(transactionId).hash); + + logger(Logging::DEBUGGING) << "Transaction " << transactionHash << " has been sent"; } catch (std::system_error& x) { - logger(Logging::WARNING) << "Unable to list transaction: " << x.what(); + logger(Logging::WARNING) << "Error while sending transaction: " << x.what(); return x.code(); + } catch (std::exception& x) { + logger(Logging::WARNING) << "Error while sending transaction: " << x.what(); + return make_error_code(CryptoNote::error::INTERNAL_WALLET_ERROR); } return std::error_code(); } -std::error_code WalletService::getTransfer(size_t globalTransferId, bool& found, TransferRpcInfo& rpcInfo) { - logger(Logging::DEBUGGING) << "getTransfer request came"; - found = false; +std::error_code WalletService::createDelayedTransaction(const CreateDelayedTransaction::Request& request, std::string& transactionHash) { try { - auto nextTxIt = std::upper_bound(transfersIndices.begin(), transfersIndices.end(), globalTransferId); - assert(nextTxIt != transfersIndices.begin()); - size_t txId = std::distance(transfersIndices.begin(), nextTxIt) - 1; - size_t fakeTxId = transfersIndices.size() - 1; + System::EventLock lk(readyEvent); - if (txId == fakeTxId) { - return std::error_code(); + validateAddresses(request.addresses, currency, logger); + validateAddresses(collectDestinationAddresses(request.transfers), currency, logger); + if (!request.changeAddress.empty()) { + validateAddresses({ request.changeAddress }, currency, logger); } - auto transferId = globalTransferId - transfersIndices[txId]; - auto transfer = wallet->getTransactionTransfer(txId, transferId); + CryptoNote::TransactionParameters sendParams; + if (!request.paymentId.empty()) { + addPaymentIdToExtra(request.paymentId, sendParams.extra); + } else { + sendParams.extra = Common::asString(Common::fromHex(request.extra)); + } - rpcInfo.type = static_cast(transfer.type); - rpcInfo.address = transfer.address; - rpcInfo.amount = transfer.amount; - found = true; + sendParams.sourceAddresses = request.addresses; + sendParams.destinations = convertWalletRpcOrdersToWalletOrders(request.transfers); + sendParams.fee = request.fee; + sendParams.mixIn = request.anonymity; + sendParams.unlockTimestamp = request.unlockTime; + sendParams.changeDestination = request.changeAddress; + + size_t transactionId = wallet.makeTransaction(sendParams); + transactionHash = Common::podToHex(wallet.getTransaction(transactionId).hash); + + logger(Logging::DEBUGGING) << "Delayed transaction " << transactionHash << " has been created"; } catch (std::system_error& x) { - logger(Logging::WARNING) << "Unable to get transfer: " << x.what(); + logger(Logging::WARNING) << "Error while creating delayed transaction: " << x.what(); return x.code(); + } catch (std::exception& x) { + logger(Logging::WARNING) << "Error while creating delayed transaction: " << x.what(); + return make_error_code(CryptoNote::error::INTERNAL_WALLET_ERROR); } return std::error_code(); } -std::error_code WalletService::getIncomingPayments(const std::vector& payments, IncomingPayments& result) { - logger(Logging::DEBUGGING) << "getIncomingPayments request came"; - +std::error_code WalletService::getDelayedTransactionHashes(std::vector& transactionHashes) { try { - for (const std::string& payment: payments) { + System::EventLock lk(readyEvent); - if (!checkPaymentId(payment)) { - return make_error_code(std::errc::argument_out_of_domain); - } + std::vector transactionIds = wallet.getDelayedTransactionIds(); + transactionHashes.reserve(transactionIds.size()); - std::string paymentString = payment; - std::transform(paymentString.begin(), paymentString.end(), paymentString.begin(), ::tolower); - auto pair = paymentIdIndex.equal_range(paymentString); + for (auto id: transactionIds) { + transactionHashes.emplace_back(Common::podToHex(wallet.getTransaction(id).hash)); + } - for (auto it = pair.first; it != pair.second; ++it) { - auto tx = wallet->getTransaction(it->transactionId); + } catch (std::system_error& x) { + logger(Logging::WARNING) << "Error while getting delayed transaction hashes: " << x.what(); + return x.code(); + } catch (std::exception& x) { + logger(Logging::WARNING) << "Error while getting delayed transaction hashes: " << x.what(); + return make_error_code(CryptoNote::error::INTERNAL_WALLET_ERROR); + } - std::string hashString = Common::podToHex(tx.hash); + return std::error_code(); +} - PaymentDetails details; - details.txHash = std::move(hashString); - details.amount = static_cast(tx.totalAmount); - details.blockHeight = tx.blockHeight; - details.unlockTime = tx.unlockTime; +std::error_code WalletService::deleteDelayedTransaction(const std::string& transactionHash) { + try { + System::EventLock lk(readyEvent); - result[it->paymentId].push_back(std::move(details)); + parseHash(transactionHash, logger); //validate transactionHash parameter + + auto idIt = transactionIdIndex.find(transactionHash); + if (idIt == transactionIdIndex.end()) { + return make_error_code(CryptoNote::error::WalletServiceErrorCode::OBJECT_NOT_FOUND); + } + + size_t transactionId = idIt->second; + wallet.rollbackUncommitedTransaction(transactionId); + + logger(Logging::DEBUGGING) << "Delayed transaction " << transactionHash << " has been canceled"; + } catch (std::system_error& x) { + logger(Logging::WARNING) << "Error while deleting delayed transaction hashes: " << x.what(); + return x.code(); + } catch (std::exception& x) { + logger(Logging::WARNING) << "Error while deleting delayed transaction hashes: " << x.what(); + return make_error_code(CryptoNote::error::INTERNAL_WALLET_ERROR); + } + + return std::error_code(); +} + +std::error_code WalletService::sendDelayedTransaction(const std::string& transactionHash) { + try { + System::EventLock lk(readyEvent); + + parseHash(transactionHash, logger); //validate transactionHash parameter + + auto idIt = transactionIdIndex.find(transactionHash); + if (idIt == transactionIdIndex.end()) { + return make_error_code(CryptoNote::error::WalletServiceErrorCode::OBJECT_NOT_FOUND); + } + + size_t transactionId = idIt->second; + wallet.commitTransaction(transactionId); + + logger(Logging::DEBUGGING) << "Delayed transaction " << transactionHash << " has been sent"; + } catch (std::system_error& x) { + logger(Logging::WARNING) << "Error while sending delayed transaction hashes: " << x.what(); + return x.code(); + } catch (std::exception& x) { + logger(Logging::WARNING) << "Error while sending delayed transaction hashes: " << x.what(); + return make_error_code(CryptoNote::error::INTERNAL_WALLET_ERROR); + } + + return std::error_code(); +} + +std::error_code WalletService::getUnconfirmedTransactionHashes(const std::vector& addresses, std::vector& transactionHashes) { + try { + System::EventLock lk(readyEvent); + + validateAddresses(addresses, currency, logger); + + std::vector transactions = wallet.getUnconfirmedTransactions(); + + TransactionsInBlockInfoFilter transactionFilter(addresses, ""); + + for (const auto& transaction: transactions) { + if (transactionFilter.checkTransaction(transaction)) { + transactionHashes.emplace_back(Common::podToHex(transaction.transaction.hash)); } } } catch (std::system_error& x) { - logger(Logging::WARNING) << "Unable to get payments: " << x.what(); + logger(Logging::WARNING) << "Error while getting unconfirmed transaction hashes: " << x.what(); return x.code(); + } catch (std::exception& x) { + logger(Logging::WARNING) << "Error while getting unconfirmed transaction hashes: " << x.what(); + return make_error_code(CryptoNote::error::INTERNAL_WALLET_ERROR); + } + + return std::error_code(); +} + +std::error_code WalletService::getStatus(uint32_t& blockCount, uint32_t& knownBlockCount, std::string& lastBlockHash, uint32_t& peerCount) { + try { + System::EventLock lk(readyEvent); + + knownBlockCount = node.getKnownBlockCount(); + peerCount = node.getPeerCount(); + blockCount = wallet.getBlockCount(); + + auto lastHashes = wallet.getBlockHashes(blockCount - 1, 1); + lastBlockHash = Common::podToHex(lastHashes.back()); + } catch (std::system_error& x) { + logger(Logging::WARNING) << "Error while getting status: " << x.what(); + return x.code(); + } catch (std::exception& x) { + logger(Logging::WARNING) << "Error while getting status: " << x.what(); + return make_error_code(CryptoNote::error::INTERNAL_WALLET_ERROR); } return std::error_code(); @@ -574,53 +1022,85 @@ std::error_code WalletService::getIncomingPayments(const std::vectorgetEvent(); - if (event.type == CryptoNote::TRANSACTION_CREATED || event.type == CryptoNote::TRANSACTION_UPDATED) { + auto event = wallet.getEvent(); + if (event.type == CryptoNote::TRANSACTION_CREATED) { size_t transactionId = event.transactionCreated.transactionIndex; - size_t transferCount = wallet->getTransactionTransferCount(transactionId); - - if (event.type == CryptoNote::TRANSACTION_CREATED) { - assert(transactionId == transfersIndices.size() - 1); - transfersIndices.push_back(transfersIndices[transactionId] + transferCount); - } else { - assert(event.type == CryptoNote::TRANSACTION_UPDATED); - assert(transactionId < transfersIndices.size() - 1); - size_t oldTransferCount = transfersIndices[transactionId + 1] - transfersIndices[transactionId]; - int change = static_cast(transferCount) - static_cast(oldTransferCount); - if (change != 0) { - for (size_t i = transactionId + 1; i < transfersIndices.size(); ++i) { - transfersIndices[i] = static_cast(static_cast(transfersIndices[i]) + change); - } - } - } - - auto tx = wallet->getTransaction(transactionId); - logger(Logging::DEBUGGING) << "Transaction updated " << transactionId << " extra size: " << tx.extra.size(); - if (tx.totalAmount < 0) { - continue; - } - - std::vector extraVector(tx.extra.begin(), tx.extra.end()); - Crypto::Hash paymentId; - if (!CryptoNote::getPaymentIdFromTxExtra(extraVector, paymentId)) { - logger(Logging::DEBUGGING) << "transaction " << transactionId << " has no payment id"; - continue; - } - - insertTransaction(transactionId, paymentId, tx.blockHeight != CryptoNote::WALLET_UNCONFIRMED_TRANSACTION_HEIGHT); - logger(Logging::DEBUGGING) << "transaction " << transactionId << " has been added to payments cache"; + transactionIdIndex.emplace(Common::podToHex(wallet.getTransaction(transactionId).hash), transactionId); } } } catch (std::system_error& e) { - logger(Logging::TRACE) << "refresh is stopped: " << e.what(); + logger(Logging::DEBUGGING) << "refresh is stopped: " << e.what(); } catch (std::exception& e) { logger(Logging::WARNING) << "exception thrown in refresh(): " << e.what(); } } -void WalletService::insertTransaction(size_t id, const Crypto::Hash& paymentIdBin, bool confirmed) { - paymentsCache.insert(PaymentItem{ Common::podToHex(paymentIdBin), id, confirmed}); +void WalletService::reset() { + PaymentService::secureSaveWallet(wallet, config.walletFile, false, false); + wallet.stop(); + wallet.shutdown(); + inited = false; + refreshContext.wait(); + + wallet.start(); + init(); +} + +void WalletService::replaceWithNewWallet(const Crypto::SecretKey& viewSecretKey) { + wallet.stop(); + wallet.shutdown(); + inited = false; + refreshContext.wait(); + + transactionIdIndex.clear(); + + wallet.start(); + wallet.initializeWithViewKey(viewSecretKey, config.walletPassword); + inited = true; +} + +std::vector WalletService::getTransactions(const Crypto::Hash& blockHash, size_t blockCount) const { + std::vector result = wallet.getTransactions(blockHash, blockCount); + if (result.empty()) { + throw std::system_error(make_error_code(CryptoNote::error::WalletServiceErrorCode::OBJECT_NOT_FOUND)); + } + + return result; +} + +std::vector WalletService::getTransactions(uint32_t firstBlockIndex, size_t blockCount) const { + std::vector result = wallet.getTransactions(firstBlockIndex, blockCount); + if (result.empty()) { + throw std::system_error(make_error_code(CryptoNote::error::WalletServiceErrorCode::OBJECT_NOT_FOUND)); + } + + return result; +} + +std::vector WalletService::getRpcTransactionHashes(const Crypto::Hash& blockHash, size_t blockCount, const TransactionsInBlockInfoFilter& filter) const { + std::vector allTransactions = getTransactions(blockHash, blockCount); + std::vector filteredTransactions = filterTransactions(allTransactions, filter); + return convertTransactionsInBlockInfoToTransactionHashesInBlockRpcInfo(filteredTransactions); +} + +std::vector WalletService::getRpcTransactionHashes(uint32_t firstBlockIndex, size_t blockCount, const TransactionsInBlockInfoFilter& filter) const { + std::vector allTransactions = getTransactions(firstBlockIndex, blockCount); + std::vector filteredTransactions = filterTransactions(allTransactions, filter); + return convertTransactionsInBlockInfoToTransactionHashesInBlockRpcInfo(filteredTransactions); +} + +std::vector WalletService::getRpcTransactions(const Crypto::Hash& blockHash, size_t blockCount, const TransactionsInBlockInfoFilter& filter) const { + std::vector allTransactions = getTransactions(blockHash, blockCount); + std::vector filteredTransactions = filterTransactions(allTransactions, filter); + return convertTransactionsInBlockInfoToTransactionsInBlockRpcInfo(filteredTransactions); +} + +std::vector WalletService::getRpcTransactions(uint32_t firstBlockIndex, size_t blockCount, const TransactionsInBlockInfoFilter& filter) const { + std::vector allTransactions = getTransactions(firstBlockIndex, blockCount); + std::vector filteredTransactions = filterTransactions(allTransactions, filter); + return convertTransactionsInBlockInfoToTransactionsInBlockRpcInfo(filteredTransactions); } } //namespace PaymentService diff --git a/src/PaymentGate/WalletService.h b/src/PaymentGate/WalletService.h index 4bad432a..57e137ae 100755 --- a/src/PaymentGate/WalletService.h +++ b/src/PaymentGate/WalletService.h @@ -35,12 +35,6 @@ namespace PaymentService { -struct SendTransactionRequest; -struct SendTransactionResponse; -struct TransferDestination; -struct TransactionRpcInfo; -struct TransferRpcInfo; - struct WalletConfiguration { std::string walletFile; std::string walletPassword; @@ -48,71 +42,74 @@ struct WalletConfiguration { void generateNewWallet(const CryptoNote::Currency ¤cy, const WalletConfiguration &conf, Logging::ILogger &logger, System::Dispatcher& dispatcher); +struct TransactionsInBlockInfoFilter; + class WalletService { public: - typedef std::map > IncomingPayments; - - explicit WalletService(const CryptoNote::Currency& currency, System::Dispatcher& sys, CryptoNote::INode& node, const WalletConfiguration& conf, Logging::ILogger& logger); + WalletService(const CryptoNote::Currency& currency, System::Dispatcher& sys, CryptoNote::INode& node, CryptoNote::IWallet& wallet, const WalletConfiguration& conf, Logging::ILogger& logger); virtual ~WalletService(); void init(); void saveWallet(); - std::error_code sendTransaction(const SendTransactionRequest& req, SendTransactionResponse& resp); - std::error_code getIncomingPayments(const std::vector& payments, IncomingPayments& result); - std::error_code getAddress(size_t index, std::string& address); - std::error_code getAddressCount(size_t& count); + std::error_code resetWallet(); + std::error_code replaceWithNewWallet(const std::string& viewSecretKey); + std::error_code createAddress(const std::string& spendSecretKeyText, std::string& address); std::error_code createAddress(std::string& address); + std::error_code createTrackingAddress(const std::string& spendPublicKeyText, std::string& address); std::error_code deleteAddress(const std::string& address); - std::error_code getActualBalance(const std::string& address, uint64_t& actualBalance); - std::error_code getPendingBalance(const std::string& address, uint64_t& pendingBalance); - std::error_code getActualBalance(uint64_t& actualBalance); - std::error_code getPendingBalance(uint64_t& pendingBalance); - std::error_code getTransactionsCount(uint64_t& txCount); - std::error_code getTransfersCount(uint64_t& trCount); - std::error_code getTransactionByTransferId(size_t transfer, size_t& transaction); - std::error_code getTransaction(size_t txId, bool& found, TransactionRpcInfo& rpcInfo); - std::error_code listTransactions(size_t startingTxId, uint32_t maxTxCount, std::vector& txsRpcInfo); - std::error_code getTransfer(size_t txId, bool& found, TransferRpcInfo& rpcInfo); + std::error_code getSpendkeys(const std::string& address, std::string& publicSpendKeyText, std::string& secretSpendKeyText); + std::error_code getBalance(const std::string& address, uint64_t& availableBalance, uint64_t& lockedAmount); + std::error_code getBalance(uint64_t& availableBalance, uint64_t& lockedAmount); + std::error_code getBlockHashes(uint32_t firstBlockIndex, uint32_t blockCount, std::vector& blockHashes); + std::error_code getViewKey(std::string& viewSecretKey); + std::error_code getTransactionHashes(const std::vector& addresses, const std::string& blockHash, + uint32_t blockCount, const std::string& paymentId, std::vector& transactionHashes); + std::error_code getTransactionHashes(const std::vector& addresses, uint32_t firstBlockIndex, + uint32_t blockCount, const std::string& paymentId, std::vector& transactionHashes); + std::error_code getTransactions(const std::vector& addresses, const std::string& blockHash, + uint32_t blockCount, const std::string& paymentId, std::vector& transactionHashes); + std::error_code getTransactions(const std::vector& addresses, uint32_t firstBlockIndex, + uint32_t blockCount, const std::string& paymentId, std::vector& transactionHashes); + std::error_code getTransaction(const std::string& transactionHash, TransactionRpcInfo& transaction); + std::error_code getAddresses(std::vector& addresses); + std::error_code sendTransaction(const SendTransaction::Request& request, std::string& transactionHash); + std::error_code createDelayedTransaction(const CreateDelayedTransaction::Request& request, std::string& transactionHash); + std::error_code getDelayedTransactionHashes(std::vector& transactionHashes); + std::error_code deleteDelayedTransaction(const std::string& transactionHash); + std::error_code sendDelayedTransaction(const std::string& transactionHash); + std::error_code getUnconfirmedTransactionHashes(const std::vector& addresses, std::vector& transactionHashes); + std::error_code getStatus(uint32_t& blockCount, uint32_t& knownBlockCount, std::string& lastBlockHash, uint32_t& peerCount); private: void refresh(); + void reset(); void loadWallet(); - void loadPaymentsCacheAndTransferIndices(); - void insertTransaction(size_t id, const Crypto::Hash& paymentIdBin, bool confirmed); + void loadTransactionIdIndex(); - void fillTransactionRpcInfo(size_t txId, const CryptoNote::WalletTransaction& tx, TransactionRpcInfo& rpcInfo); - void makeOrders(const std::vector& destinations, std::vector& transfers); + void replaceWithNewWallet(const Crypto::SecretKey& viewSecretKey); - struct PaymentItem { - std::string paymentId; - size_t transactionId; - bool confirmed; - }; + std::vector getTransactions(const Crypto::Hash& blockHash, size_t blockCount) const; + std::vector getTransactions(uint32_t firstBlockIndex, size_t blockCount) const; - typedef boost::multi_index::hashed_unique TxIdIndex; - typedef boost::multi_index::hashed_non_unique PaymentIndex; - typedef boost::multi_index::multi_index_container< - PaymentItem, - boost::multi_index::indexed_by< - TxIdIndex, - PaymentIndex - > - > PaymentsContainer; + std::vector getRpcTransactionHashes(const Crypto::Hash& blockHash, size_t blockCount, const TransactionsInBlockInfoFilter& filter) const; + std::vector getRpcTransactionHashes(uint32_t firstBlockIndex, size_t blockCount, const TransactionsInBlockInfoFilter& filter) const; - std::unique_ptr wallet; - CryptoNote::INode* node; + std::vector getRpcTransactions(const Crypto::Hash& blockHash, size_t blockCount, const TransactionsInBlockInfoFilter& filter) const; + std::vector getRpcTransactions(uint32_t firstBlockIndex, size_t blockCount, const TransactionsInBlockInfoFilter& filter) const; + + const CryptoNote::Currency& currency; + CryptoNote::IWallet& wallet; + CryptoNote::INode& node; const WalletConfiguration& config; bool inited; Logging::LoggerRef logger; - std::vector transfersIndices; System::Dispatcher& dispatcher; + System::Event readyEvent; System::ContextGroup refreshContext; - PaymentsContainer paymentsCache; - PaymentsContainer::nth_index<0>::type& txIdIndex; - PaymentsContainer::nth_index<1>::type& paymentIdIndex; + std::map transactionIdIndex; }; } //namespace PaymentService diff --git a/src/PaymentGate/WalletServiceErrorCategory.cpp b/src/PaymentGate/WalletServiceErrorCategory.cpp new file mode 100644 index 00000000..d4e47120 --- /dev/null +++ b/src/PaymentGate/WalletServiceErrorCategory.cpp @@ -0,0 +1,26 @@ +// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "WalletServiceErrorCategory.h" + +namespace CryptoNote { +namespace error { + +WalletServiceErrorCategory WalletServiceErrorCategory::INSTANCE; + +} +} diff --git a/src/PaymentGate/WalletServiceErrorCategory.h b/src/PaymentGate/WalletServiceErrorCategory.h new file mode 100644 index 00000000..efee7b71 --- /dev/null +++ b/src/PaymentGate/WalletServiceErrorCategory.h @@ -0,0 +1,75 @@ +// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include +#include + +namespace CryptoNote { +namespace error { + +enum class WalletServiceErrorCode { + WRONG_KEY_FORMAT = 1, + WRONG_PAYMENT_ID_FORMAT, + WRONG_HASH_FORMAT, + OBJECT_NOT_FOUND +}; + +// custom category: +class WalletServiceErrorCategory : public std::error_category { +public: + static WalletServiceErrorCategory INSTANCE; + + virtual const char* name() const throw() { + return "WalletServiceErrorCategory"; + } + + virtual std::error_condition default_error_condition(int ev) const throw() { + return std::error_condition(ev, *this); + } + + virtual std::string message(int ev) const { + WalletServiceErrorCode code = static_cast(ev); + + switch (code) { + case WalletServiceErrorCode::WRONG_KEY_FORMAT: return "Wrong key format"; + case WalletServiceErrorCode::WRONG_PAYMENT_ID_FORMAT: return "Wrong payment id format"; + case WalletServiceErrorCode::WRONG_HASH_FORMAT: return "Wrong block id format"; + case WalletServiceErrorCode::OBJECT_NOT_FOUND: return "Requested object not found"; + default: return "Unknown error"; + } + } + +private: + WalletServiceErrorCategory() { + } +}; + +} //namespace error +} //namespace CryptoNote + +inline std::error_code make_error_code(CryptoNote::error::WalletServiceErrorCode e) { + return std::error_code(static_cast(e), CryptoNote::error::WalletServiceErrorCategory::INSTANCE); +} + +namespace std { + +template <> +struct is_error_code_enum: public true_type {}; + +} diff --git a/src/PaymentGateService/PaymentGateService.cpp b/src/PaymentGateService/PaymentGateService.cpp index 6b3badd6..9cf149ec 100755 --- a/src/PaymentGateService/PaymentGateService.cpp +++ b/src/PaymentGateService/PaymentGateService.cpp @@ -28,6 +28,7 @@ #include "CryptoNoteCore/Core.h" #include "CryptoNoteProtocol/CryptoNoteProtocolHandler.h" #include "P2p/NetNode.h" +#include "PaymentGate/WalletFactory.h" #include #ifdef ERROR @@ -86,8 +87,8 @@ bool PaymentGateService::init(int argc, char** argv) { WalletConfiguration PaymentGateService::getWalletConfig() const { return WalletConfiguration{ - config.gateConfiguration.walletFile, - config.gateConfiguration.walletPassword + config.gateConfiguration.containerFile, + config.gateConfiguration.containerPassword }; } @@ -132,6 +133,16 @@ void PaymentGateService::stop() { } void PaymentGateService::runInProcess(Logging::LoggerRef& log) { + if (!config.coreConfig.configFolderDefaulted) { + if (!Tools::directoryExists(config.coreConfig.configFolder)) { + throw std::runtime_error("Directory does not exist: " + config.coreConfig.configFolder); + } + } else { + if (!Tools::create_directories_if_necessary(config.coreConfig.configFolder)) { + throw std::runtime_error("Can't create directory: " + config.coreConfig.configFolder); + } + } + log(Logging::INFO) << "Starting Payment Gate with local node"; CryptoNote::Currency currency = currencyBuilder.currency(); @@ -206,11 +217,13 @@ void PaymentGateService::runRpcProxy(Logging::LoggerRef& log) { void PaymentGateService::runWalletService(const CryptoNote::Currency& currency, CryptoNote::INode& node) { PaymentService::WalletConfiguration walletConfiguration{ - config.gateConfiguration.walletFile, - config.gateConfiguration.walletPassword + config.gateConfiguration.containerFile, + config.gateConfiguration.containerPassword }; - service = new PaymentService::WalletService(currency, *dispatcher, node, walletConfiguration, logger); + std::unique_ptr wallet (WalletFactory::createWallet(currency, node, *dispatcher)); + + service = new PaymentService::WalletService(currency, *dispatcher, node, *wallet, walletConfiguration, logger); std::unique_ptr serviceGuard(service); try { service->init(); @@ -221,13 +234,10 @@ void PaymentGateService::runWalletService(const CryptoNote::Currency& currency, if (config.gateConfiguration.printAddresses) { // print addresses and exit - size_t addressCount = 0; - service->getAddressCount(addressCount); - for (size_t i = 0; i < addressCount; ++i) { - std::string address; - if (service->getAddress(i, address) == std::error_code()) { - std::cout << "Address: " << address << std::endl; - } + std::vector addresses; + service->getAddresses(addresses); + for (const auto& address: addresses) { + std::cout << "Address: " << address << std::endl; } } else { PaymentService::PaymentServiceJsonRpcServer rpcServer(*dispatcher, *stopEvent, *service, logger); @@ -236,7 +246,7 @@ void PaymentGateService::runWalletService(const CryptoNote::Currency& currency, try { service->saveWallet(); } catch (std::exception& ex) { - Logging::LoggerRef(logger, "saveWallet")(Logging::WARNING, Logging::YELLOW) << "Couldn't save wallet: " << ex.what(); + Logging::LoggerRef(logger, "saveWallet")(Logging::WARNING, Logging::YELLOW) << "Couldn't save container: " << ex.what(); } } } diff --git a/src/PaymentGateService/PaymentServiceConfiguration.cpp b/src/PaymentGateService/PaymentServiceConfiguration.cpp index 3848f79d..3aa55146 100644 --- a/src/PaymentGateService/PaymentServiceConfiguration.cpp +++ b/src/PaymentGateService/PaymentServiceConfiguration.cpp @@ -28,7 +28,7 @@ namespace po = boost::program_options; namespace PaymentService { Configuration::Configuration() { - generateNewWallet = false; + generateNewContainer = false; daemonize = false; registerService = false; unregisterService = false; @@ -44,9 +44,9 @@ void Configuration::initOptions(boost::program_options::options_description& des desc.add_options() ("bind-address", po::value()->default_value("0.0.0.0"), "payment service bind address") ("bind-port", po::value()->default_value(8070), "payment service bind port") - ("wallet-file,w", po::value(), "wallet file") - ("wallet-password,p", po::value(), "wallet password") - ("generate-wallet,g", "generate new wallet file and exit") + ("container-file,w", po::value(), "container file") + ("container-password,p", po::value(), "container password") + ("generate-container,g", "generate new container file with one wallet and exit") ("daemon,d", "run as daemon in Unix or as service in Windows") #ifdef _WIN32 ("register-service", "register service and exit (Windows only)") @@ -103,16 +103,16 @@ void Configuration::init(const boost::program_options::variables_map& options) { bindPort = options["bind-port"].as(); } - if (options.count("wallet-file") != 0) { - walletFile = options["wallet-file"].as(); + if (options.count("container-file") != 0) { + containerFile = options["container-file"].as(); } - if (options.count("wallet-password") != 0) { - walletPassword = options["wallet-password"].as(); + if (options.count("container-password") != 0) { + containerPassword = options["container-password"].as(); } - if (options.count("generate-wallet") != 0) { - generateNewWallet = true; + if (options.count("generate-container") != 0) { + generateNewContainer = true; } if (options.count("address") != 0) { @@ -120,8 +120,8 @@ void Configuration::init(const boost::program_options::variables_map& options) { } if (!registerService && !unregisterService) { - if (walletFile.empty() || walletPassword.empty()) { - throw ConfigurationError("Both wallet-file and wallet-password parameters are required"); + if (containerFile.empty() || containerPassword.empty()) { + throw ConfigurationError("Both container-file and container-password parameters are required"); } } } diff --git a/src/PaymentGateService/PaymentServiceConfiguration.h b/src/PaymentGateService/PaymentServiceConfiguration.h index dc804fba..3c647d4b 100644 --- a/src/PaymentGateService/PaymentServiceConfiguration.h +++ b/src/PaymentGateService/PaymentServiceConfiguration.h @@ -39,12 +39,12 @@ struct Configuration { std::string bindAddress; uint16_t bindPort; - std::string walletFile; - std::string walletPassword; + std::string containerFile; + std::string containerPassword; std::string logFile; std::string serverRoot; - bool generateNewWallet; + bool generateNewContainer; bool daemonize; bool registerService; bool unregisterService; diff --git a/src/PaymentGateService/main.cpp b/src/PaymentGateService/main.cpp index 493f912d..ecab8e2e 100644 --- a/src/PaymentGateService/main.cpp +++ b/src/PaymentGateService/main.cpp @@ -305,7 +305,7 @@ int main(int argc, char** argv) { const auto& config = pg.getConfig(); - if (config.gateConfiguration.generateNewWallet) { + if (config.gateConfiguration.generateNewContainer) { System::Dispatcher d; generateNewWallet(pg.getCurrency(), pg.getWalletConfig(), pg.getLogger(), d); return 0; diff --git a/src/Rpc/CoreRpcServerCommandsDefinitions.h b/src/Rpc/CoreRpcServerCommandsDefinitions.h index 1dfa6710..efa437a3 100755 --- a/src/Rpc/CoreRpcServerCommandsDefinitions.h +++ b/src/Rpc/CoreRpcServerCommandsDefinitions.h @@ -277,6 +277,7 @@ struct COMMAND_RPC_GET_INFO { uint64_t incoming_connections_count; uint64_t white_peerlist_size; uint64_t grey_peerlist_size; + uint32_t last_known_block_index; void serialize(ISerializer &s) { KV_MEMBER(status) @@ -289,6 +290,7 @@ struct COMMAND_RPC_GET_INFO { KV_MEMBER(incoming_connections_count) KV_MEMBER(white_peerlist_size) KV_MEMBER(grey_peerlist_size) + KV_MEMBER(last_known_block_index) } }; }; diff --git a/src/Rpc/RpcServer.cpp b/src/Rpc/RpcServer.cpp index a6e5a2fb..4480c72f 100755 --- a/src/Rpc/RpcServer.cpp +++ b/src/Rpc/RpcServer.cpp @@ -27,6 +27,9 @@ #include "CryptoNoteCore/IBlock.h" #include "CryptoNoteCore/Miner.h" #include "CryptoNoteCore/TransactionExtra.h" + +#include "CryptoNoteProtocol/ICryptoNoteProtocolQuery.h" + #include "P2p/NetNode.h" #include "CoreRpcServerErrorCodes.h" @@ -78,50 +81,50 @@ RpcServer::HandlerFunction jsonMethod(bool (RpcServer::*handler)(typename Comman } -std::unordered_map RpcServer::s_handlers = { +std::unordered_map> RpcServer::s_handlers = { // binary handlers - { "/getblocks.bin", binMethod(&RpcServer::on_get_blocks) }, - { "/queryblocks.bin", binMethod(&RpcServer::on_query_blocks) }, - { "/queryblockslite.bin", binMethod(&RpcServer::on_query_blocks_lite) }, - { "/get_o_indexes.bin", binMethod(&RpcServer::on_get_indexes) }, - { "/getrandom_outs.bin", binMethod(&RpcServer::on_get_random_outs) }, - { "/get_pool_changes.bin", binMethod(&RpcServer::onGetPoolChanges) }, - { "/get_pool_changes_lite.bin", binMethod(&RpcServer::onGetPoolChangesLite) }, + { "/getblocks.bin", { binMethod(&RpcServer::on_get_blocks), false } }, + { "/queryblocks.bin", { binMethod(&RpcServer::on_query_blocks), false } }, + { "/queryblockslite.bin", { binMethod(&RpcServer::on_query_blocks_lite), false } }, + { "/get_o_indexes.bin", { binMethod(&RpcServer::on_get_indexes), false } }, + { "/getrandom_outs.bin", { binMethod(&RpcServer::on_get_random_outs), false } }, + { "/get_pool_changes.bin", { binMethod(&RpcServer::onGetPoolChanges), false } }, + { "/get_pool_changes_lite.bin", { binMethod(&RpcServer::onGetPoolChangesLite), false } }, // json handlers - { "/getinfo", jsonMethod(&RpcServer::on_get_info) }, - { "/getheight", jsonMethod(&RpcServer::on_get_height) }, - { "/gettransactions", jsonMethod(&RpcServer::on_get_transactions)}, - { "/sendrawtransaction", jsonMethod(&RpcServer::on_send_raw_tx) }, - { "/start_mining", jsonMethod(&RpcServer::on_start_mining) }, - { "/stop_mining", jsonMethod(&RpcServer::on_stop_mining) }, - { "/stop_daemon", jsonMethod(&RpcServer::on_stop_daemon) }, + { "/getinfo", { jsonMethod(&RpcServer::on_get_info), true } }, + { "/getheight", { jsonMethod(&RpcServer::on_get_height), true } }, + { "/gettransactions", { jsonMethod(&RpcServer::on_get_transactions), false } }, + { "/sendrawtransaction", { jsonMethod(&RpcServer::on_send_raw_tx), false } }, + { "/start_mining", { jsonMethod(&RpcServer::on_start_mining), false } }, + { "/stop_mining", { jsonMethod(&RpcServer::on_stop_mining), false } }, + { "/stop_daemon", { jsonMethod(&RpcServer::on_stop_daemon), true } }, // json rpc - { "/json_rpc", std::bind(&RpcServer::processJsonRpcRequest, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3) } + { "/json_rpc", { std::bind(&RpcServer::processJsonRpcRequest, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), true } } }; -RpcServer::RpcServer(System::Dispatcher& dispatcher, Logging::ILogger& log, core& c, NodeServer& p2p) : - HttpServer(dispatcher, log), logger(log, "RpcServer"), m_core(c), m_p2p(p2p) { +RpcServer::RpcServer(System::Dispatcher& dispatcher, Logging::ILogger& log, core& c, NodeServer& p2p, const ICryptoNoteProtocolQuery& protocolQuery) : + HttpServer(dispatcher, log), logger(log, "RpcServer"), m_core(c), m_p2p(p2p), m_protocolQuery(protocolQuery) { } void RpcServer::processRequest(const HttpRequest& request, HttpResponse& response) { auto url = request.getUrl(); - + auto it = s_handlers.find(url); if (it == s_handlers.end()) { response.setStatus(HttpResponse::STATUS_404); return; } - if (url != "/json_rpc" && !checkCoreReady()) { + if (!it->second.allowBusyCore && !isCoreReady()) { response.setStatus(HttpResponse::STATUS_500); response.setBody("Core is busy"); return; } - it->second(this, request, response); + it->second.handler(this, request, response); } bool RpcServer::processJsonRpcRequest(const HttpRequest& request, HttpResponse& response) { @@ -138,15 +141,15 @@ bool RpcServer::processJsonRpcRequest(const HttpRequest& request, HttpResponse& jsonRequest.parseRequest(request.getBody()); jsonResponse.setId(jsonRequest.getId()); // copy id - static std::unordered_map jsonRpcHandlers = { - { "getblockcount", makeMemberMethod(&RpcServer::on_getblockcount) }, - { "on_getblockhash", makeMemberMethod(&RpcServer::on_getblockhash) }, - { "getblocktemplate", makeMemberMethod(&RpcServer::on_getblocktemplate) }, - { "getcurrencyid", makeMemberMethod(&RpcServer::on_get_currency_id) }, - { "submitblock", makeMemberMethod(&RpcServer::on_submitblock) }, - { "getlastblockheader", makeMemberMethod(&RpcServer::on_get_last_block_header) }, - { "getblockheaderbyhash", makeMemberMethod(&RpcServer::on_get_block_header_by_hash) }, - { "getblockheaderbyheight", makeMemberMethod(&RpcServer::on_get_block_header_by_height) } + static std::unordered_map> jsonRpcHandlers = { + { "getblockcount", { makeMemberMethod(&RpcServer::on_getblockcount), true } }, + { "on_getblockhash", { makeMemberMethod(&RpcServer::on_getblockhash), false } }, + { "getblocktemplate", { makeMemberMethod(&RpcServer::on_getblocktemplate), false } }, + { "getcurrencyid", { makeMemberMethod(&RpcServer::on_get_currency_id), true } }, + { "submitblock", { makeMemberMethod(&RpcServer::on_submitblock), false } }, + { "getlastblockheader", { makeMemberMethod(&RpcServer::on_get_last_block_header), false } }, + { "getblockheaderbyhash", { makeMemberMethod(&RpcServer::on_get_block_header_by_hash), false } }, + { "getblockheaderbyheight", { makeMemberMethod(&RpcServer::on_get_block_header_by_height), false } } }; auto it = jsonRpcHandlers.find(jsonRequest.getMethod()); @@ -154,11 +157,11 @@ bool RpcServer::processJsonRpcRequest(const HttpRequest& request, HttpResponse& throw JsonRpcError(JsonRpc::errMethodNotFound); } - if (jsonRequest.getMethod() != "getcurrencyid" && !checkCoreReady()) { + if (!it->second.allowBusyCore && !isCoreReady()) { throw JsonRpcError(CORE_RPC_ERROR_CODE_CORE_BUSY, "Core is busy"); } - it->second(this, jsonRequest, jsonResponse); + it->second.handler(this, jsonRequest, jsonResponse); } catch (const JsonRpcError& err) { jsonResponse.setError(err); @@ -171,10 +174,8 @@ bool RpcServer::processJsonRpcRequest(const HttpRequest& request, HttpResponse& return true; } -#define CHECK_CORE_READY() - -bool RpcServer::checkCoreReady() { - return m_core.is_ready() && m_p2p.get_payload_object().isSynchronized(); +bool RpcServer::isCoreReady() { + return m_core.currency().isTestnet() || m_p2p.get_payload_object().isSynchronized(); } // @@ -219,8 +220,6 @@ bool RpcServer::on_get_blocks(const COMMAND_RPC_GET_BLOCKS_FAST::request& req, C } bool RpcServer::on_query_blocks(const COMMAND_RPC_QUERY_BLOCKS::request& req, COMMAND_RPC_QUERY_BLOCKS::response& res) { - CHECK_CORE_READY(); - uint32_t startHeight; uint32_t currentHeight; uint32_t fullOffset; @@ -238,8 +237,6 @@ bool RpcServer::on_query_blocks(const COMMAND_RPC_QUERY_BLOCKS::request& req, CO } bool RpcServer::on_query_blocks_lite(const COMMAND_RPC_QUERY_BLOCKS_LITE::request& req, COMMAND_RPC_QUERY_BLOCKS_LITE::response& res) { - CHECK_CORE_READY(); - uint32_t startHeight; uint32_t currentHeight; uint32_t fullOffset; @@ -256,8 +253,6 @@ bool RpcServer::on_query_blocks_lite(const COMMAND_RPC_QUERY_BLOCKS_LITE::reques } bool RpcServer::on_get_indexes(const COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request& req, COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response& res) { - CHECK_CORE_READY(); - std::vector outputIndexes; if (!m_core.get_tx_outputs_gindexs(req.txid, outputIndexes)) { res.status = "Failed"; @@ -271,7 +266,6 @@ bool RpcServer::on_get_indexes(const COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES:: } bool RpcServer::on_get_random_outs(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res) { - CHECK_CORE_READY(); res.status = "Failed"; if (!m_core.get_random_outs_for_amounts(req, res)) { return true; @@ -301,8 +295,6 @@ bool RpcServer::on_get_random_outs(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOU } bool RpcServer::onGetPoolChanges(const COMMAND_RPC_GET_POOL_CHANGES::request& req, COMMAND_RPC_GET_POOL_CHANGES::response& rsp) { - CHECK_CORE_READY(); - rsp.status = CORE_RPC_STATUS_OK; std::vector addedTransactions; rsp.isTailBlockActual = m_core.getPoolChanges(req.tailBlockId, req.knownTxsIds, addedTransactions, rsp.deletedTxsIds); @@ -320,8 +312,6 @@ bool RpcServer::onGetPoolChanges(const COMMAND_RPC_GET_POOL_CHANGES::request& re bool RpcServer::onGetPoolChangesLite(const COMMAND_RPC_GET_POOL_CHANGES_LITE::request& req, COMMAND_RPC_GET_POOL_CHANGES_LITE::response& rsp) { - CHECK_CORE_READY(); - rsp.status = CORE_RPC_STATUS_OK; rsp.isTailBlockActual = m_core.getPoolChangesLite(req.tailBlockId, req.knownTxsIds, rsp.addedTxs, rsp.deletedTxsIds); @@ -343,19 +333,18 @@ bool RpcServer::on_get_info(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RP res.incoming_connections_count = total_conn - res.outgoing_connections_count; res.white_peerlist_size = m_p2p.getPeerlistManager().get_white_peers_count(); res.grey_peerlist_size = m_p2p.getPeerlistManager().get_gray_peers_count(); + res.last_known_block_index = std::max(static_cast(1), m_protocolQuery.getObservedHeight()) - 1; res.status = CORE_RPC_STATUS_OK; return true; } bool RpcServer::on_get_height(const COMMAND_RPC_GET_HEIGHT::request& req, COMMAND_RPC_GET_HEIGHT::response& res) { - CHECK_CORE_READY(); res.height = m_core.get_current_blockchain_height(); res.status = CORE_RPC_STATUS_OK; return true; } bool RpcServer::on_get_transactions(const COMMAND_RPC_GET_TRANSACTIONS::request& req, COMMAND_RPC_GET_TRANSACTIONS::response& res) { - CHECK_CORE_READY(); std::vector vh; for (const auto& tx_hex_str : req.txs_hashes) { BinaryArray b; @@ -387,8 +376,6 @@ bool RpcServer::on_get_transactions(const COMMAND_RPC_GET_TRANSACTIONS::request& } bool RpcServer::on_send_raw_tx(const COMMAND_RPC_SEND_RAW_TX::request& req, COMMAND_RPC_SEND_RAW_TX::response& res) { - CHECK_CORE_READY(); - BinaryArray tx_blob; if (!fromHex(req.tx_as_hex, tx_blob)) { @@ -429,7 +416,6 @@ bool RpcServer::on_send_raw_tx(const COMMAND_RPC_SEND_RAW_TX::request& req, COMM } bool RpcServer::on_start_mining(const COMMAND_RPC_START_MINING::request& req, COMMAND_RPC_START_MINING::response& res) { - CHECK_CORE_READY(); AccountPublicAddress adr; if (!m_core.currency().parseAccountAddressString(req.miner_address, adr)) { res.status = "Failed, wrong address"; @@ -446,7 +432,6 @@ bool RpcServer::on_start_mining(const COMMAND_RPC_START_MINING::request& req, CO } bool RpcServer::on_stop_mining(const COMMAND_RPC_STOP_MINING::request& req, COMMAND_RPC_STOP_MINING::response& res) { - CHECK_CORE_READY(); if (!m_core.get_miner().stop()) { res.status = "Failed, mining not stopped"; return true; @@ -456,7 +441,6 @@ bool RpcServer::on_stop_mining(const COMMAND_RPC_STOP_MINING::request& req, COMM } bool RpcServer::on_stop_daemon(const COMMAND_RPC_STOP_DAEMON::request& req, COMMAND_RPC_STOP_DAEMON::response& res) { - CHECK_CORE_READY(); if (m_core.currency().isTestnet()) { m_p2p.sendStopSignal(); res.status = CORE_RPC_STATUS_OK; diff --git a/src/Rpc/RpcServer.h b/src/Rpc/RpcServer.h index d4567ff6..d2f42bfb 100755 --- a/src/Rpc/RpcServer.h +++ b/src/Rpc/RpcServer.h @@ -27,21 +27,28 @@ namespace CryptoNote { class core; class NodeServer; +class ICryptoNoteProtocolQuery; class RpcServer : public HttpServer { public: - RpcServer(System::Dispatcher& dispatcher, Logging::ILogger& log, core& c, NodeServer& p2p); + RpcServer(System::Dispatcher& dispatcher, Logging::ILogger& log, core& c, NodeServer& p2p, const ICryptoNoteProtocolQuery& protocolQuery); typedef std::function HandlerFunction; private: + template + struct RpcHandler { + const Handler handler; + const bool allowBusyCore; + }; + typedef void (RpcServer::*HandlerPtr)(const HttpRequest& request, HttpResponse& response); - static std::unordered_map s_handlers; + static std::unordered_map> s_handlers; virtual void processRequest(const HttpRequest& request, HttpResponse& response) override; bool processJsonRpcRequest(const HttpRequest& request, HttpResponse& response); - bool checkCoreReady(); + bool isCoreReady(); // binary handlers bool on_get_blocks(const COMMAND_RPC_GET_BLOCKS_FAST::request& req, COMMAND_RPC_GET_BLOCKS_FAST::response& res); @@ -76,6 +83,7 @@ private: Logging::LoggerRef logger; core& m_core; NodeServer& m_p2p; + const ICryptoNoteProtocolQuery& m_protocolQuery; }; } diff --git a/src/Transfers/BlockchainSynchronizer.cpp b/src/Transfers/BlockchainSynchronizer.cpp index 6517af22..b9421547 100755 --- a/src/Transfers/BlockchainSynchronizer.cpp +++ b/src/Transfers/BlockchainSynchronizer.cpp @@ -41,7 +41,10 @@ inline std::vector stringToVector(const std::string& s) { namespace CryptoNote { BlockchainSynchronizer::BlockchainSynchronizer(INode& node, const Hash& genesisBlockHash) : -m_node(node), m_genesisBlockHash(genesisBlockHash), m_currentState(State::stopped), m_futureState(State::stopped), shouldSyncConsumersPool(true) { + m_node(node), + m_genesisBlockHash(genesisBlockHash), + m_currentState(State::stopped), + m_futureState(State::stopped) { } BlockchainSynchronizer::~BlockchainSynchronizer() { @@ -57,7 +60,6 @@ void BlockchainSynchronizer::addConsumer(IBlockchainConsumer* consumer) { } m_consumers.insert(std::make_pair(consumer, std::make_shared(m_genesisBlockHash))); - shouldSyncConsumersPool = true; } bool BlockchainSynchronizer::removeConsumer(IBlockchainConsumer* consumer) { @@ -70,23 +72,81 @@ bool BlockchainSynchronizer::removeConsumer(IBlockchainConsumer* consumer) { return m_consumers.erase(consumer) > 0; } -IStreamSerializable* BlockchainSynchronizer::getConsumerState(IBlockchainConsumer* consumer) { - assert(consumer != nullptr); - - if (!(checkIfStopped() && checkIfShouldStop())) { - throw std::runtime_error("Can't get consumer state, because BlockchainSynchronizer isn't stopped"); - } - +IStreamSerializable* BlockchainSynchronizer::getConsumerState(IBlockchainConsumer* consumer) const { std::unique_lock lk(m_consumersMutex); - - auto it = m_consumers.find(consumer); - if (it == m_consumers.end()) { - return nullptr; - } - - return it->second.get(); + return getConsumerSynchronizationState(consumer); } +std::vector BlockchainSynchronizer::getConsumerKnownBlocks(IBlockchainConsumer& consumer) const { + std::unique_lock lk(m_consumersMutex); + + auto state = getConsumerSynchronizationState(&consumer); + if (state == nullptr) { + throw std::invalid_argument("Consumer not found"); + } + + return state->getKnownBlockHashes(); +} + +std::future BlockchainSynchronizer::addUnconfirmedTransaction(const ITransactionReader& transaction) { + std::unique_lock lock(m_stateMutex); + + if (m_currentState == State::stopped || m_futureState == State::stopped) { + throw std::runtime_error("Can't add transaction, because BlockchainSynchronizer is stopped"); + } + + std::promise promise; + auto future = promise.get_future(); + m_addTransactionTasks.emplace_back(&transaction, std::move(promise)); + m_hasWork.notify_one(); + + return future; +} + +std::future BlockchainSynchronizer::removeUnconfirmedTransaction(const Crypto::Hash& transactionHash) { + std::unique_lock lock(m_stateMutex); + + if (m_currentState == State::stopped || m_futureState == State::stopped) { + throw std::runtime_error("Can't remove transaction, because BlockchainSynchronizer is stopped"); + } + + std::promise promise; + auto future = promise.get_future(); + m_removeTransactionTasks.emplace_back(&transactionHash, std::move(promise)); + m_hasWork.notify_one(); + + return future; +} + +std::error_code BlockchainSynchronizer::doAddUnconfirmedTransaction(const ITransactionReader& transaction) { + std::unique_lock lk(m_consumersMutex); + + std::error_code ec; + auto addIt = m_consumers.begin(); + for (; addIt != m_consumers.end(); ++addIt) { + ec = addIt->first->addUnconfirmedTransaction(transaction); + if (ec) { + break; + } + } + + if (ec) { + auto transactionHash = transaction.getTransactionHash(); + for (auto rollbackIt = m_consumers.begin(); rollbackIt != addIt; ++rollbackIt) { + rollbackIt->first->removeUnconfirmedTransaction(transactionHash); + } + } + + return ec; +} + +void BlockchainSynchronizer::doRemoveUnconfirmedTransaction(const Crypto::Hash& transactionHash) { + std::unique_lock lk(m_consumersMutex); + + for (auto& consumer : m_consumers) { + consumer.first->removeUnconfirmedTransaction(transactionHash); + } +} void BlockchainSynchronizer::save(std::ostream& os) { os.write(reinterpret_cast(&m_genesisBlockHash), sizeof(m_genesisBlockHash)); @@ -103,16 +163,14 @@ void BlockchainSynchronizer::load(std::istream& in) { //--------------------------- FSM ------------------------------------ bool BlockchainSynchronizer::setFutureState(State s) { - return setFutureStateIf(s, std::bind( - [](State futureState, State s) -> bool { - return s > futureState; - }, std::ref(m_futureState), s)); + return setFutureStateIf(s, [this, s] { return s > m_futureState; }); } bool BlockchainSynchronizer::setFutureStateIf(State s, std::function&& pred) { std::unique_lock lk(m_stateMutex); if (pred()) { m_futureState = s; + m_hasWork.notify_one(); return true; } @@ -129,10 +187,37 @@ void BlockchainSynchronizer::actualizeFutureState() { m_node.removeObserver(this); } + while (!m_removeTransactionTasks.empty()) { + auto& task = m_removeTransactionTasks.front(); + const Crypto::Hash& transactionHash = *task.first; + auto detachedPromise = std::move(task.second); + m_removeTransactionTasks.pop_front(); + + try { + doRemoveUnconfirmedTransaction(transactionHash); + detachedPromise.set_value(); + } catch (...) { + detachedPromise.set_exception(std::current_exception()); + } + } + + while (!m_addTransactionTasks.empty()) { + auto& task = m_addTransactionTasks.front(); + const ITransactionReader& transaction = *task.first; + auto detachedPromise = std::move(task.second); + m_addTransactionTasks.pop_front(); + + try { + auto ec = doAddUnconfirmedTransaction(transaction); + detachedPromise.set_value(ec); + } catch (...) { + detachedPromise.set_exception(std::current_exception()); + } + } + m_currentState = m_futureState; switch (m_futureState) { case State::stopped: - m_futureState = State::stopped; break; case State::blockchainSync: m_futureState = State::poolSync; @@ -145,21 +230,22 @@ void BlockchainSynchronizer::actualizeFutureState() { startPoolSync(); break; case State::idle: - m_futureState = State::idle; + m_hasWork.wait(lk, [this] { + return m_futureState != State::idle || !m_removeTransactionTasks.empty() || !m_addTransactionTasks.empty(); + }); lk.unlock(); - std::this_thread::sleep_for(std::chrono::milliseconds(200)); break; default: break; } } -bool BlockchainSynchronizer::checkIfShouldStop() { +bool BlockchainSynchronizer::checkIfShouldStop() const { std::unique_lock lk(m_stateMutex); return m_futureState == State::stopped; } -bool BlockchainSynchronizer::checkIfStopped() { +bool BlockchainSynchronizer::checkIfStopped() const { std::unique_lock lk(m_stateMutex); return m_currentState == State::stopped; } @@ -178,16 +264,11 @@ void BlockchainSynchronizer::start() { throw std::runtime_error("Can't start, because BlockchainSynchronizer has no consumers"); } - if (!setFutureStateIf(State::blockchainSync, std::bind( - [](State currentState, State futureState) -> bool { - return currentState == State::stopped && futureState == State::stopped; - }, std::ref(m_currentState), std::ref(m_futureState)))) { + if (!setFutureStateIf(State::blockchainSync, [this] { return m_currentState == State::stopped && m_futureState == State::stopped; })) { throw std::runtime_error("BlockchainSynchronizer already started"); } - shouldSyncConsumersPool = true; - - workingThread.reset(new std::thread([this] {this->workingProcedure(); })); + workingThread.reset(new std::thread([this] { workingProcedure(); })); } void BlockchainSynchronizer::stop() { @@ -201,7 +282,11 @@ void BlockchainSynchronizer::stop() { workingThread.reset(); } -void BlockchainSynchronizer::lastKnownBlockHeightUpdated(uint32_t height) { +void BlockchainSynchronizer::localBlockchainUpdated(uint32_t /*height*/) { + setFutureState(State::blockchainSync); +} + +void BlockchainSynchronizer::lastKnownBlockHeightUpdated(uint32_t /*height*/) { setFutureState(State::blockchainSync); } @@ -210,52 +295,27 @@ void BlockchainSynchronizer::poolChanged() { } //--------------------------- FSM END ------------------------------------ -BlockchainSynchronizer::GetPoolRequest BlockchainSynchronizer::getUnionPoolHistory() { - GetPoolRequest request; - std::unordered_set unionHistory; - { - std::unique_lock lk(m_consumersMutex); - for (auto& consumer : m_consumers) { - std::vector consumerKnownIds; - consumer.first->getKnownPoolTxIds(consumerKnownIds); - for (auto& txId : consumerKnownIds) { - unionHistory.insert(txId); +void BlockchainSynchronizer::getPoolUnionAndIntersection(std::unordered_set& poolUnion, std::unordered_set& poolIntersection) const { + std::unique_lock lk(m_consumersMutex); + + auto itConsumers = m_consumers.begin(); + poolUnion = itConsumers->first->getKnownPoolTxIds(); + poolIntersection = itConsumers->first->getKnownPoolTxIds(); + ++itConsumers; + + for (; itConsumers != m_consumers.end(); ++itConsumers) { + const std::unordered_set& consumerKnownIds = itConsumers->first->getKnownPoolTxIds(); + + poolUnion.insert(consumerKnownIds.begin(), consumerKnownIds.end()); + + for (auto itIntersection = poolIntersection.begin(); itIntersection != poolIntersection.end();) { + if (consumerKnownIds.count(*itIntersection) == 0) { + itIntersection = poolIntersection.erase(itIntersection); + } else { + ++itIntersection; } } } - - for (auto& id : unionHistory) { - request.knownTxIds.push_back(id); - } - - request.lastKnownBlock = lastBlockId; - return request; -} - -BlockchainSynchronizer::GetPoolRequest BlockchainSynchronizer::getIntersectedPoolHistory() { - GetPoolRequest request; - { - std::unique_lock lk(m_consumersMutex); - auto it = m_consumers.begin(); - - it->first->getKnownPoolTxIds(request.knownTxIds); - ++it; - - for (; it != m_consumers.end(); ++it) { //iterate over consumers - std::vector consumerKnownIds; - it->first->getKnownPoolTxIds(consumerKnownIds); - for (auto itReq = request.knownTxIds.begin(); itReq != request.knownTxIds.end();) { //iterate over intersection - if (std::count(consumerKnownIds.begin(), consumerKnownIds.end(), *itReq) == 0) { //consumer doesn't contain id from intersection, so delete this id from intersection - itReq = request.knownTxIds.erase(itReq); - } else { - ++itReq; - } - } - } - } - - request.lastKnownBlock = lastBlockId; - return request; } BlockchainSynchronizer::GetBlocksRequest BlockchainSynchronizer::getCommonHistory() { @@ -290,42 +350,34 @@ void BlockchainSynchronizer::startBlockchainSync() { try { if (!req.knownBlocks.empty()) { - asyncOperationCompleted = std::promise(); - asyncOperationWaitFuture = asyncOperationCompleted.get_future(); + auto queryBlocksCompleted = std::promise(); + auto queryBlocksWaitFuture = queryBlocksCompleted.get_future(); - m_node.queryBlocks - (std::move(req.knownBlocks), req.syncStart.timestamp, response.newBlocks, response.startHeight, - std::bind(&BlockchainSynchronizer::onGetBlocksCompleted, this, std::placeholders::_1)); + m_node.queryBlocks( + std::move(req.knownBlocks), + req.syncStart.timestamp, + response.newBlocks, + response.startHeight, + [&queryBlocksCompleted](std::error_code ec) { + auto detachedPromise = std::move(queryBlocksCompleted); + detachedPromise.set_value(ec); + }); - std::error_code ec = asyncOperationWaitFuture.get(); + std::error_code ec = queryBlocksWaitFuture.get(); if (ec) { - setFutureStateIf(State::idle, std::bind( - [](State futureState) -> bool { - return futureState != State::stopped; - }, std::ref(m_futureState))); + setFutureStateIf(State::idle, [this] { return m_futureState != State::stopped; }); m_observerManager.notify(&IBlockchainSynchronizerObserver::synchronizationCompleted, ec); } else { processBlocks(response); } } - } catch (std::exception& e) { - std::cout << e.what() << std::endl; - setFutureStateIf(State::idle, std::bind( - [](State futureState) -> bool { - return futureState != State::stopped; - }, std::ref(m_futureState))); - m_observerManager.notify( - &IBlockchainSynchronizerObserver::synchronizationCompleted, - std::make_error_code(std::errc::invalid_argument)); + } catch (std::exception&) { + setFutureStateIf(State::idle, [this] { return m_futureState != State::stopped; }); + m_observerManager.notify(&IBlockchainSynchronizerObserver::synchronizationCompleted, std::make_error_code(std::errc::invalid_argument)); } } -void BlockchainSynchronizer::onGetBlocksCompleted(std::error_code ec) { - decltype(asyncOperationCompleted) detachedPromise = std::move(asyncOperationCompleted); - detachedPromise.set_value(ec); -} - void BlockchainSynchronizer::processBlocks(GetBlocksResponse& response) { BlockchainInterval interval; interval.startHeight = response.startHeight; @@ -335,6 +387,7 @@ void BlockchainSynchronizer::processBlocks(GetBlocksResponse& response) { if (checkIfShouldStop()) { break; } + CompleteBlock completeBlock; completeBlock.blockHash = block.blockHash; interval.blocks.push_back(completeBlock.blockHash); @@ -344,19 +397,11 @@ void BlockchainSynchronizer::processBlocks(GetBlocksResponse& response) { try { for (const auto& txShortInfo : block.txsShortInfo) { - completeBlock.transactions.push_back(createTransactionPrefix(txShortInfo.txPrefix, - reinterpret_cast(txShortInfo.txId))); + completeBlock.transactions.push_back(createTransactionPrefix(txShortInfo.txPrefix, reinterpret_cast(txShortInfo.txId))); } } catch (std::exception&) { - setFutureStateIf(State::idle, std::bind( - [](State futureState) -> bool { - return futureState != State::stopped; - }, std::ref(m_futureState))); - - m_observerManager.notify( - &IBlockchainSynchronizerObserver::synchronizationCompleted, - std::make_error_code(std::errc::invalid_argument)); - + setFutureStateIf(State::idle, [this] { return m_futureState != State::stopped; }); + m_observerManager.notify(&IBlockchainSynchronizerObserver::synchronizationCompleted, std::make_error_code(std::errc::invalid_argument)); return; } } @@ -373,22 +418,18 @@ void BlockchainSynchronizer::processBlocks(GetBlocksResponse& response) { switch (result) { case UpdateConsumersResult::errorOccurred: - if (setFutureStateIf(State::idle, std::bind( - [](State futureState) -> bool { - return futureState != State::stopped; - }, std::ref(m_futureState)))) { - m_observerManager.notify( - &IBlockchainSynchronizerObserver::synchronizationCompleted, - std::make_error_code(std::errc::invalid_argument)); + if (setFutureStateIf(State::idle, [this] { return m_futureState != State::stopped; })) { + m_observerManager.notify(&IBlockchainSynchronizerObserver::synchronizationCompleted, std::make_error_code(std::errc::invalid_argument)); } - break; + case UpdateConsumersResult::nothingChanged: if (m_node.getLastKnownBlockHeight() != m_node.getLastLocalBlockHeight()) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } else { break; } + case UpdateConsumersResult::addedNewBlocks: setFutureState(State::blockchainSync); m_observerManager.notify( @@ -404,12 +445,11 @@ void BlockchainSynchronizer::processBlocks(GetBlocksResponse& response) { } if (checkIfShouldStop()) { //Sic! - m_observerManager.notify( - &IBlockchainSynchronizerObserver::synchronizationCompleted, - std::make_error_code(std::errc::interrupted)); + m_observerManager.notify(&IBlockchainSynchronizerObserver::synchronizationCompleted, std::make_error_code(std::errc::interrupted)); } } +/// \pre m_consumersMutex is locked BlockchainSynchronizer::UpdateConsumersResult BlockchainSynchronizer::updateConsumers(const BlockchainInterval& interval, const std::vector& blocks) { bool smthChanged = false; @@ -424,15 +464,9 @@ BlockchainSynchronizer::UpdateConsumersResult BlockchainSynchronizer::updateCons if (result.hasNewBlocks) { uint32_t startOffset = result.newBlockHeight - interval.startHeight; // update consumer - if (kv.first->onNewBlocks( - blocks.data() + startOffset, - result.newBlockHeight, - static_cast(blocks.size()) - startOffset)) { + if (kv.first->onNewBlocks(blocks.data() + startOffset, result.newBlockHeight, static_cast(blocks.size()) - startOffset)) { // update state if consumer succeeded - kv.second->addBlocks( - interval.blocks.data() + startOffset, - result.newBlockHeight, - static_cast(interval.blocks.size()) - startOffset); + kv.second->addBlocks(interval.blocks.data() + startOffset, result.newBlockHeight, static_cast(interval.blocks.size()) - startOffset); smthChanged = true; } else { return UpdateConsumersResult::errorOccurred; @@ -444,57 +478,41 @@ BlockchainSynchronizer::UpdateConsumersResult BlockchainSynchronizer::updateCons } void BlockchainSynchronizer::startPoolSync() { + std::unordered_set unionPoolHistory; + std::unordered_set intersectedPoolHistory; + getPoolUnionAndIntersection(unionPoolHistory, intersectedPoolHistory); + + GetPoolRequest unionRequest; + unionRequest.knownTxIds.assign(unionPoolHistory.begin(), unionPoolHistory.end()); + unionRequest.lastKnownBlock = lastBlockId; + GetPoolResponse unionResponse; - GetPoolRequest unionRequest = getUnionPoolHistory(); - - asyncOperationCompleted = std::promise(); - asyncOperationWaitFuture = asyncOperationCompleted.get_future(); - unionResponse.isLastKnownBlockActual = false; - m_node.getPoolSymmetricDifference(std::move(unionRequest.knownTxIds), std::move(unionRequest.lastKnownBlock), unionResponse.isLastKnownBlockActual, - unionResponse.newTxs, unionResponse.deletedTxIds, std::bind(&BlockchainSynchronizer::onGetPoolChanges, this, std::placeholders::_1)); - - std::error_code ec = asyncOperationWaitFuture.get(); + std::error_code ec = getPoolSymmetricDifferenceSync(std::move(unionRequest), unionResponse); if (ec) { - setFutureStateIf(State::idle, std::bind( - [](State futureState) -> bool { - return futureState != State::stopped; - }, std::ref(m_futureState))); - m_observerManager.notify( - &IBlockchainSynchronizerObserver::synchronizationCompleted, - ec); + setFutureStateIf(State::idle, [this] { return m_futureState != State::stopped; }); + m_observerManager.notify(&IBlockchainSynchronizerObserver::synchronizationCompleted, ec); } else { //get union ok if (!unionResponse.isLastKnownBlockActual) { //bc outdated setFutureState(State::blockchainSync); } else { - if (!shouldSyncConsumersPool) { //usual case, start pool processing - m_observerManager.notify( - &IBlockchainSynchronizerObserver::synchronizationCompleted, - processPoolTxs(unionResponse)); - } else {// first launch, we should sync consumers' pools, so let's ask for intersection + if (unionPoolHistory == intersectedPoolHistory) { //usual case, start pool processing + m_observerManager.notify(&IBlockchainSynchronizerObserver::synchronizationCompleted, processPoolTxs(unionResponse)); + } else { + GetPoolRequest intersectionRequest; + intersectionRequest.knownTxIds.assign(intersectedPoolHistory.begin(), intersectedPoolHistory.end()); + intersectionRequest.lastKnownBlock = lastBlockId; + GetPoolResponse intersectionResponse; - GetPoolRequest intersectionRequest = getIntersectedPoolHistory(); - - asyncOperationCompleted = std::promise(); - asyncOperationWaitFuture = asyncOperationCompleted.get_future(); - intersectionResponse.isLastKnownBlockActual = false; - m_node.getPoolSymmetricDifference(std::move(intersectionRequest.knownTxIds), std::move(intersectionRequest.lastKnownBlock), intersectionResponse.isLastKnownBlockActual, - intersectionResponse.newTxs, intersectionResponse.deletedTxIds, std::bind(&BlockchainSynchronizer::onGetPoolChanges, this, std::placeholders::_1)); - - std::error_code ec2 = asyncOperationWaitFuture.get(); + std::error_code ec2 = getPoolSymmetricDifferenceSync(std::move(intersectionRequest), intersectionResponse); if (ec2) { - setFutureStateIf(State::idle, std::bind( - [](State futureState) -> bool { - return futureState != State::stopped; - }, std::ref(m_futureState))); - m_observerManager.notify( - &IBlockchainSynchronizerObserver::synchronizationCompleted, - ec2); + setFutureStateIf(State::idle, [this] { return m_futureState != State::stopped; }); + m_observerManager.notify(&IBlockchainSynchronizerObserver::synchronizationCompleted, ec2); } else { //get intersection ok if (!intersectionResponse.isLastKnownBlockActual) { //bc outdated setFutureState(State::blockchainSync); @@ -503,13 +521,7 @@ void BlockchainSynchronizer::startPoolSync() { std::error_code ec3 = processPoolTxs(intersectionResponse); //notify about error, or success - m_observerManager.notify( - &IBlockchainSynchronizerObserver::synchronizationCompleted, - ec3); - - if (!ec3) { - shouldSyncConsumersPool = false; - } + m_observerManager.notify(&IBlockchainSynchronizerObserver::synchronizationCompleted, ec3); } } } @@ -517,9 +529,22 @@ void BlockchainSynchronizer::startPoolSync() { } } -void BlockchainSynchronizer::onGetPoolChanges(std::error_code ec) { - decltype(asyncOperationCompleted) detachedPromise = std::move(asyncOperationCompleted); - detachedPromise.set_value(ec); +std::error_code BlockchainSynchronizer::getPoolSymmetricDifferenceSync(GetPoolRequest&& request, GetPoolResponse& response) { + auto promise = std::promise(); + auto future = promise.get_future(); + + m_node.getPoolSymmetricDifference( + std::move(request.knownTxIds), + std::move(request.lastKnownBlock), + response.isLastKnownBlockActual, + response.newTxs, + response.deletedTxIds, + [&promise](std::error_code ec) { + auto detachedPromise = std::move(promise); + detachedPromise.set_value(ec); + }); + + return future.get(); } std::error_code BlockchainSynchronizer::processPoolTxs(GetPoolResponse& response) { @@ -541,4 +566,20 @@ std::error_code BlockchainSynchronizer::processPoolTxs(GetPoolResponse& response return error; } +///pre: m_consumersMutex is locked +SynchronizationState* BlockchainSynchronizer::getConsumerSynchronizationState(IBlockchainConsumer* consumer) const { + assert(consumer != nullptr); + + if (!(checkIfStopped() && checkIfShouldStop())) { + throw std::runtime_error("Can't get consumer state, because BlockchainSynchronizer isn't stopped"); + } + + auto it = m_consumers.find(consumer); + if (it == m_consumers.end()) { + return nullptr; + } + + return it->second.get(); +} + } diff --git a/src/Transfers/BlockchainSynchronizer.h b/src/Transfers/BlockchainSynchronizer.h index 272f6016..68ed5a44 100755 --- a/src/Transfers/BlockchainSynchronizer.h +++ b/src/Transfers/BlockchainSynchronizer.h @@ -41,7 +41,11 @@ public: // IBlockchainSynchronizer virtual void addConsumer(IBlockchainConsumer* consumer) override; virtual bool removeConsumer(IBlockchainConsumer* consumer) override; - virtual IStreamSerializable* getConsumerState(IBlockchainConsumer* consumer) override; + virtual IStreamSerializable* getConsumerState(IBlockchainConsumer* consumer) const override; + virtual std::vector getConsumerKnownBlocks(IBlockchainConsumer& consumer) const override; + + virtual std::future addUnconfirmedTransaction(const ITransactionReader& transaction) override; + virtual std::future removeUnconfirmedTransaction(const Crypto::Hash& transactionHash) override; virtual void start() override; virtual void stop() override; @@ -51,6 +55,7 @@ public: virtual void load(std::istream& in) override; // INodeObserver + virtual void localBlockchainUpdated(uint32_t height) override; virtual void lastKnownBlockHeightUpdated(uint32_t height) override; virtual void poolChanged() override; @@ -81,7 +86,6 @@ private: Crypto::Hash lastKnownBlock; }; - enum class State { //prioritized finite states idle = 0, //DO poolSync = 1, //NOT @@ -99,25 +103,26 @@ private: void startPoolSync(); void startBlockchainSync(); - void onGetBlocksCompleted(std::error_code ec); void processBlocks(GetBlocksResponse& response); UpdateConsumersResult updateConsumers(const BlockchainInterval& interval, const std::vector& blocks); - void onGetPoolChanges(std::error_code ec); std::error_code processPoolTxs(GetPoolResponse& response); - + std::error_code getPoolSymmetricDifferenceSync(GetPoolRequest&& request, GetPoolResponse& response); + std::error_code doAddUnconfirmedTransaction(const ITransactionReader& transaction); + void doRemoveUnconfirmedTransaction(const Crypto::Hash& transactionHash); + ///second parameter is used only in case of errors returned into callback from INode, such as aborted or connection lost bool setFutureState(State s); bool setFutureStateIf(State s, std::function&& pred); void actualizeFutureState(); - bool checkIfShouldStop(); - bool checkIfStopped(); + bool checkIfShouldStop() const; + bool checkIfStopped() const; void workingProcedure(); GetBlocksRequest getCommonHistory(); - GetPoolRequest getUnionPoolHistory(); - GetPoolRequest getIntersectedPoolHistory(); + void getPoolUnionAndIntersection(std::unordered_set& poolUnion, std::unordered_set& poolIntersection) const; + SynchronizationState* getConsumerSynchronizationState(IBlockchainConsumer* consumer) const ; typedef std::map> ConsumersMap; @@ -130,14 +135,12 @@ private: State m_currentState; State m_futureState; std::unique_ptr workingThread; + std::list>> m_addTransactionTasks; + std::list>> m_removeTransactionTasks; - std::future asyncOperationWaitFuture; - std::promise asyncOperationCompleted; - - std::mutex m_consumersMutex; - std::mutex m_stateMutex; - - bool shouldSyncConsumersPool; + mutable std::mutex m_consumersMutex; + mutable std::mutex m_stateMutex; + std::condition_variable m_hasWork; }; } diff --git a/src/Transfers/IBlockchainSynchronizer.h b/src/Transfers/IBlockchainSynchronizer.h index dcb5fbe5..823d8202 100755 --- a/src/Transfers/IBlockchainSynchronizer.h +++ b/src/Transfers/IBlockchainSynchronizer.h @@ -18,7 +18,9 @@ #pragma once #include +#include #include +#include #include "crypto/crypto.h" #include "CryptoNoteCore/CryptoNoteBasic.h" @@ -37,16 +39,29 @@ public: virtual void synchronizationCompleted(std::error_code result) {} }; -class IBlockchainConsumer { +class IBlockchainConsumerObserver; + +class IBlockchainConsumer : public IObservable { public: virtual ~IBlockchainConsumer() {} virtual SynchronizationStart getSyncStart() = 0; - virtual void getKnownPoolTxIds(std::vector& ids) = 0; + virtual const std::unordered_set& getKnownPoolTxIds() const = 0; virtual void onBlockchainDetach(uint32_t height) = 0; virtual bool onNewBlocks(const CompleteBlock* blocks, uint32_t startHeight, uint32_t count) = 0; virtual std::error_code onPoolUpdated(const std::vector>& addedTransactions, const std::vector& deletedTransactions) = 0; + + virtual std::error_code addUnconfirmedTransaction(const ITransactionReader& transaction) = 0; + virtual void removeUnconfirmedTransaction(const Crypto::Hash& transactionHash) = 0; }; +class IBlockchainConsumerObserver { +public: + virtual void onBlocksAdded(IBlockchainConsumer* consumer, const std::vector& blockHashes) {} + virtual void onBlockchainDetach(IBlockchainConsumer* consumer, uint32_t blockIndex) {} + virtual void onTransactionDeleteBegin(IBlockchainConsumer* consumer, Crypto::Hash transactionHash) {} + virtual void onTransactionDeleteEnd(IBlockchainConsumer* consumer, Crypto::Hash transactionHash) {} + virtual void onTransactionUpdated(IBlockchainConsumer* consumer, const Crypto::Hash& transactionHash, const std::vector& containers) {} +}; class IBlockchainSynchronizer : public IObservable, @@ -54,7 +69,11 @@ class IBlockchainSynchronizer : public: virtual void addConsumer(IBlockchainConsumer* consumer) = 0; virtual bool removeConsumer(IBlockchainConsumer* consumer) = 0; - virtual IStreamSerializable* getConsumerState(IBlockchainConsumer* consumer) = 0; + virtual IStreamSerializable* getConsumerState(IBlockchainConsumer* consumer) const = 0; + virtual std::vector getConsumerKnownBlocks(IBlockchainConsumer& consumer) const = 0; + + virtual std::future addUnconfirmedTransaction(const ITransactionReader& transaction) = 0; + virtual std::future removeUnconfirmedTransaction(const Crypto::Hash& transactionHash) = 0; virtual void start() = 0; virtual void stop() = 0; diff --git a/src/Transfers/SynchronizationState.cpp b/src/Transfers/SynchronizationState.cpp index 4b3d2224..b0d31744 100755 --- a/src/Transfers/SynchronizationState.cpp +++ b/src/Transfers/SynchronizationState.cpp @@ -103,6 +103,10 @@ uint32_t SynchronizationState::getHeight() const { return static_cast(m_blockchain.size()); } +const std::vector& SynchronizationState::getKnownBlockHashes() const { + return m_blockchain; +} + void SynchronizationState::save(std::ostream& os) { StdOutputStream stream(os); CryptoNote::BinaryOutputStreamSerializer s(stream); diff --git a/src/Transfers/SynchronizationState.h b/src/Transfers/SynchronizationState.h index 13990fa0..16e84785 100755 --- a/src/Transfers/SynchronizationState.h +++ b/src/Transfers/SynchronizationState.h @@ -47,6 +47,7 @@ public: void detach(uint32_t height); void addBlocks(const Crypto::Hash* blockHashes, uint32_t height, uint32_t count); uint32_t getHeight() const; + const std::vector& getKnownBlockHashes() const; // IStreamSerializable virtual void save(std::ostream& os) override; diff --git a/src/Transfers/TransfersConsumer.cpp b/src/Transfers/TransfersConsumer.cpp index 050494dd..0613f2ca 100755 --- a/src/Transfers/TransfersConsumer.cpp +++ b/src/Transfers/TransfersConsumer.cpp @@ -16,13 +16,15 @@ // along with Bytecoin. If not, see . #include "TransfersConsumer.h" -#include "CommonTypes.h" +#include + +#include "CommonTypes.h" #include "Common/BlockingQueue.h" #include "CryptoNoteCore/CryptoNoteFormatUtils.h" #include "CryptoNoteCore/TransactionApi.h" -#include "IWalletLegacy.h" +#include "IWallet.h" #include "INode.h" #include @@ -90,6 +92,17 @@ void findMyOutputs( } } +std::vector getBlockHashes(const CryptoNote::CompleteBlock* blocks, size_t count) { + std::vector result; + result.reserve(count); + + for (size_t i = 0; i < count; ++i) { + result.push_back(blocks[i].blockHash); + } + + return result; +} + } namespace CryptoNote { @@ -133,6 +146,19 @@ void TransfersConsumer::getSubscriptions(std::vector& subs } } +void TransfersConsumer::initTransactionPool(const std::unordered_set& uncommitedTransactions) { + for (auto itSubscriptions = m_subscriptions.begin(); itSubscriptions != m_subscriptions.end(); ++itSubscriptions) { + std::vector unconfirmedTransactions; + itSubscriptions->second->getContainer().getUnconfirmedTransactions(unconfirmedTransactions); + + for (auto itTransactions = unconfirmedTransactions.begin(); itTransactions != unconfirmedTransactions.end(); ++itTransactions) { + if (uncommitedTransactions.count(*itTransactions) == 0) { + m_poolTxs.emplace(*itTransactions); + } + } + } +} + void TransfersConsumer::updateSyncStart() { SynchronizationStart start; @@ -153,6 +179,8 @@ SynchronizationStart TransfersConsumer::getSyncStart() { } void TransfersConsumer::onBlockchainDetach(uint32_t height) { + m_observerManager.notify(&IBlockchainConsumerObserver::onBlockchainDetach, this, height); + for (const auto& kv : m_subscriptions) { kv.second->onBlockchainDetach(height); } @@ -253,24 +281,23 @@ bool TransfersConsumer::onNewBlocks(const CompleteBlock* blocks, uint32_t startH } } + std::vector blockHashes = getBlockHashes(blocks, count); if (!processingError) { + m_observerManager.notify(&IBlockchainConsumerObserver::onBlocksAdded, this, blockHashes); + // sort by block height and transaction index in block std::sort(preprocessedTransactions.begin(), preprocessedTransactions.end(), [](const PreprocessedTx& a, const PreprocessedTx& b) { return std::tie(a.blockInfo.height, a.blockInfo.transactionIndex) < std::tie(b.blockInfo.height, b.blockInfo.transactionIndex); }); for (const auto& tx : preprocessedTransactions) { - processingError = processTransaction(tx.blockInfo, *tx.tx, tx); - if (processingError) { - break; - } + processTransaction(tx.blockInfo, *tx.tx, tx); } - } - - if (processingError) { + } else { forEachSubscription([&](TransfersSubscription& sub) { sub.onError(processingError, startHeight); }); + return false; } @@ -285,44 +312,55 @@ bool TransfersConsumer::onNewBlocks(const CompleteBlock* blocks, uint32_t startH std::error_code TransfersConsumer::onPoolUpdated(const std::vector>& addedTransactions, const std::vector& deletedTransactions) { TransactionBlockInfo unconfirmedBlockInfo; unconfirmedBlockInfo.timestamp = 0; - unconfirmedBlockInfo.height = WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT; + unconfirmedBlockInfo.height = WALLET_UNCONFIRMED_TRANSACTION_HEIGHT; + std::error_code processingError; for (auto& cryptonoteTransaction : addedTransactions) { + m_poolTxs.emplace(cryptonoteTransaction->getTransactionHash()); processingError = processTransaction(unconfirmedBlockInfo, *cryptonoteTransaction.get()); if (processingError) { - break; - } - } + for (auto& sub : m_subscriptions) { + sub.second->onError(processingError, WALLET_UNCONFIRMED_TRANSACTION_HEIGHT); + } - if (processingError) { - for (auto& sub : m_subscriptions) { - sub.second->onError(processingError, WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT); + return processingError; } - - return processingError; } for (auto& deletedTxHash : deletedTransactions) { - for (auto& sub: m_subscriptions) { + m_poolTxs.erase(deletedTxHash); + + m_observerManager.notify(&IBlockchainConsumerObserver::onTransactionDeleteBegin, this, deletedTxHash); + for (auto& sub : m_subscriptions) { sub.second->deleteUnconfirmedTransaction(*reinterpret_cast(&deletedTxHash)); } + + m_observerManager.notify(&IBlockchainConsumerObserver::onTransactionDeleteEnd, this, deletedTxHash); } return std::error_code(); } -void TransfersConsumer::getKnownPoolTxIds(std::vector& ids) { - ids.clear(); - std::unordered_set knownIds; - for (auto& sub : m_subscriptions) { - std::vector subscriptionUnconfirmedTxIds; - sub.second->getContainer().getUnconfirmedTransactions(subscriptionUnconfirmedTxIds); - knownIds.insert(subscriptionUnconfirmedTxIds.begin(), subscriptionUnconfirmedTxIds.end()); - } - - ids.assign(knownIds.begin(), knownIds.end()); +const std::unordered_set& TransfersConsumer::getKnownPoolTxIds() const { + return m_poolTxs; } +std::error_code TransfersConsumer::addUnconfirmedTransaction(const ITransactionReader& transaction) { + TransactionBlockInfo unconfirmedBlockInfo; + unconfirmedBlockInfo.height = WALLET_UNCONFIRMED_TRANSACTION_HEIGHT; + unconfirmedBlockInfo.timestamp = 0; + unconfirmedBlockInfo.transactionIndex = 0; + + return processTransaction(unconfirmedBlockInfo, transaction); +} + +void TransfersConsumer::removeUnconfirmedTransaction(const Crypto::Hash& transactionHash) { + m_observerManager.notify(&IBlockchainConsumerObserver::onTransactionDeleteBegin, this, transactionHash); + for (auto& subscription : m_subscriptions) { + subscription.second->deleteUnconfirmedTransaction(transactionHash); + } + m_observerManager.notify(&IBlockchainConsumerObserver::onTransactionDeleteEnd, this, transactionHash); +} std::error_code createTransfers( const AccountKeys& account, @@ -353,7 +391,7 @@ std::error_code createTransfers( info.type = outType; info.transactionPublicKey = txPubKey; info.outputInTransaction = idx; - info.globalOutputIndex = (blockInfo.height == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) ? + info.globalOutputIndex = (blockInfo.height == WALLET_UNCONFIRMED_TRANSACTION_HEIGHT) ? UNCONFIRMED_TRANSACTION_GLOBAL_OUTPUT_INDEX : globalIdxs[idx]; if (outType == TransactionTypes::OutputType::Key) { @@ -399,7 +437,7 @@ std::error_code TransfersConsumer::preprocessOutputs(const TransactionBlockInfo& std::error_code errorCode; auto txHash = tx.getTransactionHash(); - if (blockInfo.height != WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) { + if (blockInfo.height != WALLET_UNCONFIRMED_TRANSACTION_HEIGHT) { errorCode = getGlobalIndices(reinterpret_cast(txHash), info.globalIdxs); if (errorCode) { return errorCode; @@ -427,50 +465,52 @@ std::error_code TransfersConsumer::processTransaction(const TransactionBlockInfo return ec; } - return processTransaction(blockInfo, tx, info); + processTransaction(blockInfo, tx, info); + return std::error_code(); } - -std::error_code TransfersConsumer::processTransaction(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx, const PreprocessInfo& info) { - std::error_code errorCode; +void TransfersConsumer::processTransaction(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx, const PreprocessInfo& info) { std::vector emptyOutputs; + std::vector transactionContainers; + bool someContainerUpdated = false; for (auto& kv : m_subscriptions) { auto it = info.outputs.find(kv.first); auto& subscriptionOutputs = (it == info.outputs.end()) ? emptyOutputs : it->second; - errorCode = processOutputs(blockInfo, *kv.second, tx, subscriptionOutputs, info.globalIdxs); - if (errorCode) { - return errorCode; + + bool containerContainsTx; + bool containerUpdated; + processOutputs(blockInfo, *kv.second, tx, subscriptionOutputs, info.globalIdxs, containerContainsTx, containerUpdated); + someContainerUpdated = someContainerUpdated || containerUpdated; + if (containerContainsTx) { + transactionContainers.emplace_back(&kv.second->getContainer()); } } - return std::error_code(); -} - - - -std::error_code TransfersConsumer::processOutputs(const TransactionBlockInfo& blockInfo, TransfersSubscription& sub, - const ITransactionReader& tx, const std::vector& transfers, const std::vector& globalIdxs) { - - if (blockInfo.height != WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) { - TransactionInformation subscribtionTxInfo; - int64_t txBalance; - if (sub.getContainer().getTransactionInformation(tx.getTransactionHash(), subscribtionTxInfo, txBalance)) { - if (subscribtionTxInfo.blockHeight == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) { - // pool->blockchain - sub.markTransactionConfirmed(blockInfo, tx.getTransactionHash(), globalIdxs); - return std::error_code(); - } else { - // - Subscription already has this transaction as confirmed, so why are we here? - // - Because, for instance, some another subscription doesn't have this transactions, so it is given to us again. - return std::error_code(); - } - } + if (someContainerUpdated) { + m_observerManager.notify(&IBlockchainConsumerObserver::onTransactionUpdated, this, tx.getTransactionHash(), transactionContainers); } - - sub.addTransaction(blockInfo, tx, transfers); - return std::error_code(); } +void TransfersConsumer::processOutputs(const TransactionBlockInfo& blockInfo, TransfersSubscription& sub, const ITransactionReader& tx, + const std::vector& transfers, const std::vector& globalIdxs, bool& contains, bool& updated) { + + TransactionInformation subscribtionTxInfo; + contains = sub.getContainer().getTransactionInformation(tx.getTransactionHash(), subscribtionTxInfo); + updated = false; + + if (contains) { + if (subscribtionTxInfo.blockHeight == WALLET_UNCONFIRMED_TRANSACTION_HEIGHT && blockInfo.height != WALLET_UNCONFIRMED_TRANSACTION_HEIGHT) { + // pool->blockchain + sub.markTransactionConfirmed(blockInfo, tx.getTransactionHash(), globalIdxs); + updated = true; + } else { + assert(subscribtionTxInfo.blockHeight == blockInfo.height); + } + } else { + updated = sub.addTransaction(blockInfo, tx, transfers); + contains = updated; + } +} std::error_code TransfersConsumer::getGlobalIndices(const Hash& transactionHash, std::vector& outsGlobalIndices) { std::promise prom; diff --git a/src/Transfers/TransfersConsumer.h b/src/Transfers/TransfersConsumer.h index 0463d421..e6889672 100755 --- a/src/Transfers/TransfersConsumer.h +++ b/src/Transfers/TransfersConsumer.h @@ -32,8 +32,7 @@ namespace CryptoNote { class INode; -class TransfersConsumer : public IBlockchainConsumer { - +class TransfersConsumer: public IObservableImpl { public: TransfersConsumer(const CryptoNote::Currency& currency, INode& node, const Crypto::SecretKey& viewSecret); @@ -43,13 +42,18 @@ public: bool removeSubscription(const AccountPublicAddress& address); ITransfersSubscription* getSubscription(const AccountPublicAddress& acc); void getSubscriptions(std::vector& subscriptions); + + void initTransactionPool(const std::unordered_set& uncommitedTransactions); // IBlockchainConsumer virtual SynchronizationStart getSyncStart() override; virtual void onBlockchainDetach(uint32_t height) override; virtual bool onNewBlocks(const CompleteBlock* blocks, uint32_t startHeight, uint32_t count) override; virtual std::error_code onPoolUpdated(const std::vector>& addedTransactions, const std::vector& deletedTransactions) override; - virtual void getKnownPoolTxIds(std::vector& ids) override; + virtual const std::unordered_set& getKnownPoolTxIds() const override; + + virtual std::error_code addUnconfirmedTransaction(const ITransactionReader& transaction) override; + virtual void removeUnconfirmedTransaction(const Crypto::Hash& transactionHash) override; private: @@ -67,9 +71,9 @@ private: std::error_code preprocessOutputs(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx, PreprocessInfo& info); std::error_code processTransaction(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx); - std::error_code processTransaction(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx, const PreprocessInfo& info); - std::error_code processOutputs(const TransactionBlockInfo& blockInfo, TransfersSubscription& sub, const ITransactionReader& tx, - const std::vector& outputs, const std::vector& globalIdxs); + void processTransaction(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx, const PreprocessInfo& info); + void processOutputs(const TransactionBlockInfo& blockInfo, TransfersSubscription& sub, const ITransactionReader& tx, + const std::vector& outputs, const std::vector& globalIdxs, bool& contains, bool& updated); std::error_code getGlobalIndices(const Crypto::Hash& transactionHash, std::vector& outsGlobalIndices); @@ -80,6 +84,7 @@ private: // map { spend public key -> subscription } std::unordered_map> m_subscriptions; std::unordered_set m_spendKeys; + std::unordered_set m_poolTxs; INode& m_node; const CryptoNote::Currency& m_currency; diff --git a/src/Transfers/TransfersContainer.cpp b/src/Transfers/TransfersContainer.cpp index d4cdcbd9..72fb2a7c 100755 --- a/src/Transfers/TransfersContainer.cpp +++ b/src/Transfers/TransfersContainer.cpp @@ -256,11 +256,11 @@ bool TransfersContainer::addTransactionOutputs(const TransactionBlockInfo& block assert(result.second); } else { if (info.type == TransactionTypes::OutputType::Multisignature) { - SpentOutputDescriptor descriptor(transfer); - if (m_availableTransfers.get().count(descriptor) > 0 || - m_spentTransfers.get().count(descriptor) > 0) { - throw std::runtime_error("Transfer already exists"); - } + SpentOutputDescriptor descriptor(transfer); + if (m_availableTransfers.get().count(descriptor) > 0 || + m_spentTransfers.get().count(descriptor) > 0) { + throw std::runtime_error("Transfer already exists"); + } } auto result = m_availableTransfers.emplace(std::move(info)); @@ -341,10 +341,10 @@ bool TransfersContainer::addTransactionInputs(const TransactionBlockInfo& block, outputDescriptorIndex.erase(availableOutputIt); inputsAdded = true; - } + } } else { assert(inputType == TransactionTypes::InputType::Generating); - } + } } return inputsAdded; @@ -361,7 +361,7 @@ bool TransfersContainer::deleteUnconfirmedTransaction(const Hash& transactionHas } else { deleteTransactionTransfers(it->transactionHash); m_transactions.erase(it); - return true; + return true; } } @@ -401,12 +401,12 @@ bool TransfersContainer::markTransactionConfirmed(const TransactionBlockInfo& bl transfer.globalOutputIndex = globalIndices[transfer.outputInTransaction]; if (transfer.type == TransactionTypes::OutputType::Multisignature) { - SpentOutputDescriptor descriptor(transfer); - if (m_availableTransfers.get().count(descriptor) > 0 || - m_spentTransfers.get().count(descriptor) > 0) { - // This exception breaks TransfersContainer consistency - throw std::runtime_error("Transfer already exists"); - } + SpentOutputDescriptor descriptor(transfer); + if (m_availableTransfers.get().count(descriptor) > 0 || + m_spentTransfers.get().count(descriptor) > 0) { + // This exception breaks TransfersContainer consistency + throw std::runtime_error("Transfer already exists"); + } } auto result = m_availableTransfers.emplace(std::move(transfer)); @@ -417,7 +417,7 @@ bool TransfersContainer::markTransactionConfirmed(const TransactionBlockInfo& bl if (transfer.type == TransactionTypes::OutputType::Key) { updateTransfersVisibility(transfer.keyImage); - } + } } auto& spendingTransactionIndex = m_spentTransfers.get(); @@ -430,7 +430,7 @@ bool TransfersContainer::markTransactionConfirmed(const TransactionBlockInfo& bl spendingTransactionIndex.replace(transferIt, transfer); } - return true; + return true; } /** @@ -472,7 +472,7 @@ void TransfersContainer::deleteTransactionTransfers(const Hash& transactionHash) updateTransfersVisibility(keyImage); } else { it = transactionTransfersIndex.erase(it); - } + } } } @@ -643,7 +643,7 @@ void TransfersContainer::getOutputs(std::vector& t } } -bool TransfersContainer::getTransactionInformation(const Hash& transactionHash, TransactionInformation& info, int64_t& txBalance) const { +bool TransfersContainer::getTransactionInformation(const Hash& transactionHash, TransactionInformation& info, uint64_t* amountIn, uint64_t* amountOut) const { std::lock_guard lk(m_mutex); auto it = m_transactions.find(transactionHash); if (it == m_transactions.end()) { @@ -652,32 +652,35 @@ bool TransfersContainer::getTransactionInformation(const Hash& transactionHash, info = *it; - int64_t amountOut = 0; - if (info.blockHeight == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) { - auto unconfirmedOutputsRange = m_unconfirmedTransfers.get().equal_range(transactionHash); - for (auto it = unconfirmedOutputsRange.first; it != unconfirmedOutputsRange.second; ++it) { - amountOut += static_cast(it->amount); - } - } else { - auto availableOutputsRange = m_availableTransfers.get().equal_range(transactionHash); - for (auto it = availableOutputsRange.first; it != availableOutputsRange.second; ++it) { - amountOut += static_cast(it->amount); - } + if (amountOut != nullptr) { + *amountOut = 0; - auto spentOutputsRange = m_spentTransfers.get().equal_range(transactionHash); - for (auto it = spentOutputsRange.first; it != spentOutputsRange.second; ++it) { - amountOut += static_cast(it->amount); + if (info.blockHeight == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) { + auto unconfirmedOutputsRange = m_unconfirmedTransfers.get().equal_range(transactionHash); + for (auto it = unconfirmedOutputsRange.first; it != unconfirmedOutputsRange.second; ++it) { + *amountOut += it->amount; + } + } else { + auto availableOutputsRange = m_availableTransfers.get().equal_range(transactionHash); + for (auto it = availableOutputsRange.first; it != availableOutputsRange.second; ++it) { + *amountOut += it->amount; + } + + auto spentOutputsRange = m_spentTransfers.get().equal_range(transactionHash); + for (auto it = spentOutputsRange.first; it != spentOutputsRange.second; ++it) { + *amountOut += it->amount; + } } } - int64_t amountIn = 0; - auto rangeInputs = m_spentTransfers.get().equal_range(transactionHash); - for (auto it = rangeInputs.first; it != rangeInputs.second; ++it) { - amountIn += static_cast(it->amount); + if (amountIn != nullptr) { + *amountIn = 0; + auto rangeInputs = m_spentTransfers.get().equal_range(transactionHash); + for (auto it = rangeInputs.first; it != rangeInputs.second; ++it) { + *amountIn += it->amount; + } } - txBalance = amountOut - amountIn; - return true; } diff --git a/src/Transfers/TransfersContainer.h b/src/Transfers/TransfersContainer.h index 6cdb9ff0..de104450 100755 --- a/src/Transfers/TransfersContainer.h +++ b/src/Transfers/TransfersContainer.h @@ -165,7 +165,8 @@ public: virtual size_t transactionsCount() const override; virtual uint64_t balance(uint32_t flags) const override; virtual void getOutputs(std::vector& transfers, uint32_t flags) const override; - virtual bool getTransactionInformation(const Crypto::Hash& transactionHash, TransactionInformation& info, int64_t& txBalance) const override; + virtual bool getTransactionInformation(const Crypto::Hash& transactionHash, TransactionInformation& info, + uint64_t* amountIn = nullptr, uint64_t* amountOut = nullptr) const override; virtual std::vector getTransactionOutputs(const Crypto::Hash& transactionHash, uint32_t flags) const override; //only type flags are feasible for this function virtual std::vector getTransactionInputs(const Crypto::Hash& transactionHash, uint32_t flags) const override; diff --git a/src/Transfers/TransfersSubscription.cpp b/src/Transfers/TransfersSubscription.cpp index 2f1e3784..2e2d0cc3 100755 --- a/src/Transfers/TransfersSubscription.cpp +++ b/src/Transfers/TransfersSubscription.cpp @@ -52,12 +52,14 @@ const AccountKeys& TransfersSubscription::getKeys() const { return subscription.keys; } -void TransfersSubscription::addTransaction(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx, +bool TransfersSubscription::addTransaction(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx, const std::vector& transfersList) { bool added = transfers.addTransaction(blockInfo, tx, transfersList); if (added) { m_observerManager.notify(&ITransfersObserver::onTransactionUpdated, this, tx.getTransactionHash()); } + + return added; } AccountPublicAddress TransfersSubscription::getAddress() { @@ -69,8 +71,9 @@ ITransfersContainer& TransfersSubscription::getContainer() { } void TransfersSubscription::deleteUnconfirmedTransaction(const Hash& transactionHash) { - transfers.deleteUnconfirmedTransaction(transactionHash); - m_observerManager.notify(&ITransfersObserver::onTransactionDeleted, this, transactionHash); + if (transfers.deleteUnconfirmedTransaction(transactionHash)) { + m_observerManager.notify(&ITransfersObserver::onTransactionDeleted, this, transactionHash); + } } void TransfersSubscription::markTransactionConfirmed(const TransactionBlockInfo& block, const Hash& transactionHash, diff --git a/src/Transfers/TransfersSubscription.h b/src/Transfers/TransfersSubscription.h index f8a46861..85a6466c 100755 --- a/src/Transfers/TransfersSubscription.h +++ b/src/Transfers/TransfersSubscription.h @@ -32,7 +32,7 @@ public: void onError(const std::error_code& ec, uint32_t height); bool advanceHeight(uint32_t height); const AccountKeys& getKeys() const; - void addTransaction(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx, + bool addTransaction(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx, const std::vector& transfers); void deleteUnconfirmedTransaction(const Crypto::Hash& transactionHash); diff --git a/src/Transfers/TransfersSynchronizer.cpp b/src/Transfers/TransfersSynchronizer.cpp index 961d2051..5d2adb12 100755 --- a/src/Transfers/TransfersSynchronizer.cpp +++ b/src/Transfers/TransfersSynchronizer.cpp @@ -41,13 +41,21 @@ TransfersSyncronizer::~TransfersSyncronizer() { } } +void TransfersSyncronizer::initTransactionPool(const std::unordered_set& uncommitedTransactions) { + for (auto it = m_consumers.begin(); it != m_consumers.end(); ++it) { + it->second->initTransactionPool(uncommitedTransactions); + } +} + ITransfersSubscription& TransfersSyncronizer::addSubscription(const AccountSubscription& acc) { auto it = m_consumers.find(acc.keys.address.viewPublicKey); if (it == m_consumers.end()) { std::unique_ptr consumer( new TransfersConsumer(m_currency, m_node, acc.keys.viewSecretKey)); + m_sync.addConsumer(consumer.get()); + consumer->addObserver(this); it = m_consumers.insert(std::make_pair(acc.keys.address.viewPublicKey, std::move(consumer))).first; } @@ -62,6 +70,8 @@ bool TransfersSyncronizer::removeSubscription(const AccountPublicAddress& acc) { if (it->second->removeSubscription(acc)) { m_sync.removeConsumer(it->second.get()); m_consumers.erase(it); + + m_subscribers.erase(acc.viewPublicKey); } return true; @@ -75,7 +85,68 @@ void TransfersSyncronizer::getSubscriptions(std::vector& s ITransfersSubscription* TransfersSyncronizer::getSubscription(const AccountPublicAddress& acc) { auto it = m_consumers.find(acc.viewPublicKey); - return (it == m_consumers.end()) ? 0 : it->second->getSubscription(acc); + return (it == m_consumers.end()) ? nullptr : it->second->getSubscription(acc); +} + +std::vector TransfersSyncronizer::getViewKeyKnownBlocks(const Crypto::PublicKey& publicViewKey) { + auto it = m_consumers.find(publicViewKey); + if (it == m_consumers.end()) { + throw std::invalid_argument("Consumer not found"); + } + + return m_sync.getConsumerKnownBlocks(*it->second); +} + +void TransfersSyncronizer::onBlocksAdded(IBlockchainConsumer* consumer, const std::vector& blockHashes) { + auto it = findSubscriberForConsumer(consumer); + if (it != m_subscribers.end()) { + it->second->notify(&ITransfersSynchronizerObserver::onBlocksAdded, it->first, blockHashes); + } +} + +void TransfersSyncronizer::onBlockchainDetach(IBlockchainConsumer* consumer, uint32_t blockIndex) { + auto it = findSubscriberForConsumer(consumer); + if (it != m_subscribers.end()) { + it->second->notify(&ITransfersSynchronizerObserver::onBlockchainDetach, it->first, blockIndex); + } +} + +void TransfersSyncronizer::onTransactionDeleteBegin(IBlockchainConsumer* consumer, Crypto::Hash transactionHash) { + auto it = findSubscriberForConsumer(consumer); + if (it != m_subscribers.end()) { + it->second->notify(&ITransfersSynchronizerObserver::onTransactionDeleteBegin, it->first, transactionHash); + } +} + +void TransfersSyncronizer::onTransactionDeleteEnd(IBlockchainConsumer* consumer, Crypto::Hash transactionHash) { + auto it = findSubscriberForConsumer(consumer); + if (it != m_subscribers.end()) { + it->second->notify(&ITransfersSynchronizerObserver::onTransactionDeleteEnd, it->first, transactionHash); + } +} + +void TransfersSyncronizer::onTransactionUpdated(IBlockchainConsumer* consumer, const Crypto::Hash& transactionHash, + const std::vector& containers) { + + auto it = findSubscriberForConsumer(consumer); + if (it != m_subscribers.end()) { + it->second->notify(&ITransfersSynchronizerObserver::onTransactionUpdated, it->first, transactionHash, containers); + } +} + +void TransfersSyncronizer::subscribeConsumerNotifications(const Crypto::PublicKey& viewPublicKey, ITransfersSynchronizerObserver* observer) { + auto it = m_subscribers.find(viewPublicKey); + if (it != m_subscribers.end()) { + it->second->add(observer); + return; + } + + auto insertedIt = m_subscribers.emplace(viewPublicKey, std::unique_ptr(new SubscribersNotifier())).first; + insertedIt->second->add(observer); +} + +void TransfersSyncronizer::unsubscribeConsumerNotifications(const Crypto::PublicKey& viewPublicKey, ITransfersSynchronizerObserver* observer) { + m_subscribers.at(viewPublicKey)->remove(observer); } void TransfersSyncronizer::save(std::ostream& os) { @@ -233,4 +304,30 @@ void TransfersSyncronizer::load(std::istream& is) { } +bool TransfersSyncronizer::findViewKeyForConsumer(IBlockchainConsumer* consumer, Crypto::PublicKey& viewKey) const { + //since we have only couple of consumers linear complexity is fine + auto it = std::find_if(m_consumers.begin(), m_consumers.end(), [consumer] (const ConsumersContainer::value_type& subscription) { + return subscription.second.get() == consumer; + }); + + if (it == m_consumers.end()) { + return false; + } + + viewKey = it->first; + return true; +} + +TransfersSyncronizer::SubscribersContainer::const_iterator TransfersSyncronizer::findSubscriberForConsumer(IBlockchainConsumer* consumer) const { + Crypto::PublicKey viewKey; + if (findViewKeyForConsumer(consumer, viewKey)) { + auto it = m_subscribers.find(viewKey); + if (it != m_subscribers.end()) { + return it; + } + } + + return m_subscribers.end(); +} + } diff --git a/src/Transfers/TransfersSynchronizer.h b/src/Transfers/TransfersSynchronizer.h index c66a8bed..38358567 100644 --- a/src/Transfers/TransfersSynchronizer.h +++ b/src/Transfers/TransfersSynchronizer.h @@ -17,6 +17,7 @@ #pragma once +#include "Common/ObserverManager.h" #include "ITransfersSynchronizer.h" #include "IBlockchainSynchronizer.h" #include "TypeHelpers.h" @@ -34,31 +35,50 @@ namespace CryptoNote { class TransfersConsumer; class INode; -class TransfersSyncronizer : public ITransfersSynchronizer { +class TransfersSyncronizer : public ITransfersSynchronizer, public IBlockchainConsumerObserver { public: - TransfersSyncronizer(const CryptoNote::Currency& currency, IBlockchainSynchronizer& sync, INode& node); - ~TransfersSyncronizer(); + virtual ~TransfersSyncronizer(); + + void initTransactionPool(const std::unordered_set& uncommitedTransactions); // ITransfersSynchronizer virtual ITransfersSubscription& addSubscription(const AccountSubscription& acc) override; virtual bool removeSubscription(const AccountPublicAddress& acc) override; virtual void getSubscriptions(std::vector& subscriptions) override; virtual ITransfersSubscription* getSubscription(const AccountPublicAddress& acc) override; + virtual std::vector getViewKeyKnownBlocks(const Crypto::PublicKey& publicViewKey) override; + + void subscribeConsumerNotifications(const Crypto::PublicKey& viewPublicKey, ITransfersSynchronizerObserver* observer); + void unsubscribeConsumerNotifications(const Crypto::PublicKey& viewPublicKey, ITransfersSynchronizerObserver* observer); // IStreamSerializable virtual void save(std::ostream& os) override; virtual void load(std::istream& in) override; private: - // map { view public key -> consumer } - std::unordered_map> m_consumers; + typedef std::unordered_map> ConsumersContainer; + ConsumersContainer m_consumers; + + typedef Tools::ObserverManager SubscribersNotifier; + typedef std::unordered_map> SubscribersContainer; + SubscribersContainer m_subscribers; // std::unordered_map> m_subscriptions; IBlockchainSynchronizer& m_sync; INode& m_node; const CryptoNote::Currency& m_currency; + + virtual void onBlocksAdded(IBlockchainConsumer* consumer, const std::vector& blockHashes) override; + virtual void onBlockchainDetach(IBlockchainConsumer* consumer, uint32_t blockIndex) override; + virtual void onTransactionDeleteBegin(IBlockchainConsumer* consumer, Crypto::Hash transactionHash) override; + virtual void onTransactionDeleteEnd(IBlockchainConsumer* consumer, Crypto::Hash transactionHash) override; + virtual void onTransactionUpdated(IBlockchainConsumer* consumer, const Crypto::Hash& transactionHash, + const std::vector& containers) override; + + bool findViewKeyForConsumer(IBlockchainConsumer* consumer, Crypto::PublicKey& viewKey) const; + SubscribersContainer::const_iterator findSubscriberForConsumer(IBlockchainConsumer* consumer) const; }; } diff --git a/src/Wallet/WalletErrors.h b/src/Wallet/WalletErrors.h index f8eaa5d3..93149a95 100644 --- a/src/Wallet/WalletErrors.h +++ b/src/Wallet/WalletErrors.h @@ -46,7 +46,11 @@ enum WalletErrorCodes { INDEX_OUT_OF_RANGE, ADDRESS_ALREADY_EXISTS, TRACKING_MODE, - WRONG_PARAMETERS + WRONG_PARAMETERS, + OBJECT_NOT_FOUND, + WALLET_NOT_FOUND, + CHANGE_ADDRESS_REQUIRED, + CHANGE_ADDRESS_NOT_FOUND }; // custom category: @@ -85,6 +89,10 @@ public: case ADDRESS_ALREADY_EXISTS: return "Address already exists"; case TRACKING_MODE: return "The wallet is in tracking mode"; case WRONG_PARAMETERS: return "Wrong parameters passed"; + case OBJECT_NOT_FOUND: return "Object not found"; + case WALLET_NOT_FOUND: return "Requested wallet not found"; + case CHANGE_ADDRESS_REQUIRED: return "Change address required"; + case CHANGE_ADDRESS_NOT_FOUND: return "Change address not found"; default: return "Unknown error"; } } @@ -100,3 +108,10 @@ private: inline std::error_code make_error_code(CryptoNote::error::WalletErrorCodes e) { return std::error_code(static_cast(e), CryptoNote::error::WalletErrorCategory::INSTANCE); } + +namespace std { + +template <> +struct is_error_code_enum: public true_type {}; + +} diff --git a/src/Wallet/WalletGreen.cpp b/src/Wallet/WalletGreen.cpp index a0d6347e..939a199a 100755 --- a/src/Wallet/WalletGreen.cpp +++ b/src/Wallet/WalletGreen.cpp @@ -25,10 +25,13 @@ #include #include #include + #include +#include #include "ITransaction.h" +#include "Common/ScopeExit.h" #include "Common/ShuffleGenerator.h" #include "Common/StdInputStream.h" #include "Common/StdOutputStream.h" @@ -42,6 +45,7 @@ #include "Transfers/TransfersContainer.h" #include "WalletSerialization.h" #include "WalletErrors.h" +#include "WalletUtils.h" using namespace Common; using namespace Crypto; @@ -59,16 +63,24 @@ void parseAddressString(const std::string& string, const CryptoNote::Currency& c } } -bool validateAddress(const std::string& address, const CryptoNote::Currency& currency) { - CryptoNote::AccountPublicAddress ignore; - return currency.parseAccountAddressString(address, ignore); +void validateAddresses(const std::vector& addresses, const CryptoNote::Currency& currency) { + for (const auto& address: addresses) { + if (!CryptoNote::validateAddress(address, currency)) { + throw std::system_error(make_error_code(CryptoNote::error::BAD_ADDRESS)); + } + } } -void validateAddresses(const std::vector& destinations, const CryptoNote::Currency& currency) { - for (const auto& destination: destinations) { - if (!validateAddress(destination.address, currency)) { +void validateOrders(const std::vector& orders, const CryptoNote::Currency& currency) { + for (const auto& order: orders) { + if (!CryptoNote::validateAddress(order.address, currency)) { throw std::system_error(make_error_code(CryptoNote::error::BAD_ADDRESS)); } + + if (order.amount >= static_cast(std::numeric_limits::max())) { + throw std::system_error(make_error_code(CryptoNote::error::WRONG_AMOUNT), + "Order amount must not exceed " + std::to_string(std::numeric_limits::max())); + } } } @@ -153,14 +165,14 @@ size_t getTransactionSize(const ITransactionReader& transaction) { return transaction.getTransactionData().size(); } -std::vector convertOrdersToTransfer(const std::vector& orders) { +std::vector convertOrdersToTransfers(const std::vector& orders) { std::vector transfers; transfers.reserve(orders.size()); for (const auto& order: orders) { WalletTransfer transfer; - if (order.amount > std::numeric_limits::max()) { + if (order.amount > static_cast(std::numeric_limits::max())) { throw std::system_error(make_error_code(CryptoNote::error::WRONG_AMOUNT), "Order amount must not exceed " + std::to_string(std::numeric_limits::max())); } @@ -197,7 +209,7 @@ uint64_t calculateDonationAmount(uint64_t freeAmount, uint64_t donationThreshold uint64_t pushDonationTransferIfPossible(const DonationSettings& donation, uint64_t freeAmount, uint64_t dustThreshold, std::vector& destinations) { uint64_t donationAmount = 0; if (!donation.address.empty() && donation.threshold != 0) { - if (donation.threshold > std::numeric_limits::max()) { + if (donation.threshold > static_cast(std::numeric_limits::max())) { throw std::system_error(make_error_code(error::WRONG_AMOUNT), "Donation threshold must not exceed " + std::to_string(std::numeric_limits::max())); } @@ -211,6 +223,16 @@ uint64_t pushDonationTransferIfPossible(const DonationSettings& donation, uint64 return donationAmount; } +CryptoNote::AccountPublicAddress parseAccountAddressString(const std::string& addressString, const CryptoNote::Currency& currency) { + CryptoNote::AccountPublicAddress address; + + if (!currency.parseAccountAddressString(addressString, address)) { + throw std::system_error(make_error_code(CryptoNote::error::BAD_ADDRESS)); + } + + return address; +} + } namespace CryptoNote { @@ -219,10 +241,15 @@ WalletGreen::WalletGreen(System::Dispatcher& dispatcher, const Currency& currenc m_dispatcher(dispatcher), m_currency(currency), m_node(node), + m_stopped(false), + m_blockchainSynchronizerStarted(false), m_blockchainSynchronizer(node, currency.genesisBlockHash()), m_synchronizer(currency, m_blockchainSynchronizer, node), m_eventOccurred(m_dispatcher), m_readyEvent(m_dispatcher), + m_state(WalletState::NOT_INITIALIZED), + m_actualBalance(0), + m_pendingBalance(0), m_transactionSoftLockTime(transactionSoftLockTime) { m_upperTransactionSizeLimit = m_currency.blockGrantedFullRewardZone() * 125 / 100 - m_currency.minerTxBlobReservedSize(); @@ -262,7 +289,11 @@ void WalletGreen::shutdown() { } void WalletGreen::doShutdown() { - m_blockchainSynchronizer.stop(); + if (m_walletsContainer.size() != 0) { + m_synchronizer.unsubscribeConsumerNotifications(m_viewPublicKey, this); + } + + stopBlockchainSynchronizer(); m_blockchainSynchronizer.removeObserver(this); clearCaches(); @@ -279,14 +310,14 @@ void WalletGreen::clearCaches() { std::for_each(subscriptions.begin(), subscriptions.end(), [this] (const AccountPublicAddress& address) { m_synchronizer.removeSubscription(address); }); m_walletsContainer.clear(); - m_spentOutputs.clear(); m_unlockTransactionsJob.clear(); m_transactions.clear(); m_transfers.clear(); - m_change.clear(); + m_uncommitedTransactions.clear(); m_actualBalance = 0; m_pendingBalance = 0; m_fusionTxsCache.clear(); + m_blockchain.clear(); } void WalletGreen::initWithKeys(const Crypto::PublicKey& viewPublicKey, const Crypto::SecretKey& viewSecretKey, const std::string& password) { @@ -300,6 +331,9 @@ void WalletGreen::initWithKeys(const Crypto::PublicKey& viewPublicKey, const Cry m_viewSecretKey = viewSecretKey; m_password = password; + assert(m_blockchain.empty()); + m_blockchain.push_back(m_currency.genesisBlockHash()); + m_blockchainSynchronizer.addObserver(this); m_state = WalletState::INITIALIZED; @@ -309,18 +343,27 @@ void WalletGreen::save(std::ostream& destination, bool saveDetails, bool saveCac throwIfNotInitialized(); throwIfStopped(); - if (m_walletsContainer.get().size() != 0) { - m_blockchainSynchronizer.stop(); - } + stopBlockchainSynchronizer(); unsafeSave(destination, saveDetails, saveCache); - if (m_walletsContainer.get().size() != 0) { - m_blockchainSynchronizer.start(); - } + startBlockchainSynchronizer(); } void WalletGreen::unsafeSave(std::ostream& destination, bool saveDetails, bool saveCache) { + WalletTransactions transactions; + WalletTransfers transfers; + + if (saveDetails && !saveCache) { + filterOutTransactions(transactions, transfers, [] (const WalletTransaction& tx) { + return tx.state == WalletTransactionState::CREATED || tx.state == WalletTransactionState::DELETED; + }); + } else if (saveDetails) { + filterOutTransactions(transactions, transfers, [] (const WalletTransaction& tx) { + return tx.state == WalletTransactionState::DELETED; + }); + } + WalletSerializer s( *this, m_viewPublicKey, @@ -329,12 +372,11 @@ void WalletGreen::unsafeSave(std::ostream& destination, bool saveDetails, bool s m_pendingBalance, m_walletsContainer, m_synchronizer, - m_spentOutputs, m_unlockTransactionsJob, - m_change, - m_transactions, - m_transfers, - m_transactionSoftLockTime + transactions, + transfers, + m_transactionSoftLockTime, + m_uncommitedTransactions ); StdOutputStream output(destination); @@ -348,14 +390,18 @@ void WalletGreen::load(std::istream& source, const std::string& password) { throwIfStopped(); - if (m_walletsContainer.get().size() != 0) { - m_blockchainSynchronizer.stop(); - } + stopBlockchainSynchronizer(); unsafeLoad(source, password); + assert(m_blockchain.empty()); if (m_walletsContainer.get().size() != 0) { - m_blockchainSynchronizer.start(); + m_synchronizer.subscribeConsumerNotifications(m_viewPublicKey, this); + getViewKeyKnownBlocks(m_viewPublicKey); + + startBlockchainSynchronizer(); + } else { + m_blockchain.push_back(m_currency.genesisBlockHash()); } m_state = WalletState::INITIALIZED; @@ -370,12 +416,11 @@ void WalletGreen::unsafeLoad(std::istream& source, const std::string& password) m_pendingBalance, m_walletsContainer, m_synchronizer, - m_spentOutputs, m_unlockTransactionsJob, - m_change, m_transactions, m_transfers, - m_transactionSoftLockTime + m_transactionSoftLockTime, + m_uncommitedTransactions ); StdInputStream inputStream(source); @@ -408,7 +453,7 @@ std::string WalletGreen::getAddress(size_t index) const { throwIfStopped(); if (index >= m_walletsContainer.get().size()) { - throw std::system_error(std::make_error_code(std::errc::invalid_argument)); + throw std::system_error(make_error_code(std::errc::invalid_argument)); } const WalletRecord& wallet = m_walletsContainer.get()[index]; @@ -420,13 +465,27 @@ KeyPair WalletGreen::getAddressSpendKey(size_t index) const { throwIfStopped(); if (index >= m_walletsContainer.get().size()) { - throw std::system_error(std::make_error_code(std::errc::invalid_argument)); + throw std::system_error(make_error_code(std::errc::invalid_argument)); } const WalletRecord& wallet = m_walletsContainer.get()[index]; return {wallet.spendPublicKey, wallet.spendSecretKey}; } +KeyPair WalletGreen::getAddressSpendKey(const std::string& address) const { + throwIfNotInitialized(); + throwIfStopped(); + + CryptoNote::AccountPublicAddress pubAddr = parseAddress(address); + + auto it = m_walletsContainer.get().find(pubAddr.spendPublicKey); + if (it == m_walletsContainer.get().end()) { + throw std::system_error(make_error_code(error::OBJECT_NOT_FOUND)); + } + + return {it->spendPublicKey, it->spendSecretKey}; +} + KeyPair WalletGreen::getViewKey() const { throwIfNotInitialized(); throwIfStopped(); @@ -452,33 +511,44 @@ std::string WalletGreen::createAddress(const Crypto::SecretKey& spendSecretKey) } std::string WalletGreen::createAddress(const Crypto::PublicKey& spendPublicKey) { + if (!Crypto::check_key(spendPublicKey)) { + throw std::system_error(make_error_code(error::WRONG_PARAMETERS), "Wrong public key format"); + } + return doCreateAddress(spendPublicKey, NULL_SECRET_KEY, 0); } std::string WalletGreen::doCreateAddress(const Crypto::PublicKey& spendPublicKey, const Crypto::SecretKey& spendSecretKey, uint64_t creationTimestamp) { + assert(creationTimestamp <= std::numeric_limits::max() - m_currency.blockFutureTimeLimit()); + throwIfNotInitialized(); throwIfStopped(); - if (m_walletsContainer.get().size() != 0) { - m_blockchainSynchronizer.stop(); - } + stopBlockchainSynchronizer(); + std::string address; try { - addWallet(spendPublicKey, spendSecretKey, creationTimestamp); - } catch (std::exception&) { - if (m_walletsContainer.get().size() != 0) { - m_blockchainSynchronizer.start(); - } + address = addWallet(spendPublicKey, spendSecretKey, creationTimestamp); + auto currentTime = static_cast(time(nullptr)); + if (creationTimestamp + m_currency.blockFutureTimeLimit() < currentTime) { + std::string password = m_password; + std::stringstream ss; + unsafeSave(ss, true, false); + shutdown(); + load(ss, password); + } + } catch (std::exception&) { + startBlockchainSynchronizer(); throw; } - m_blockchainSynchronizer.start(); + startBlockchainSynchronizer(); - return m_currency.accountAddressAsString({ spendPublicKey, m_viewPublicKey }); + return address; } -void WalletGreen::addWallet(const Crypto::PublicKey& spendPublicKey, const Crypto::SecretKey& spendSecretKey, uint64_t creationTimestamp) { +std::string WalletGreen::addWallet(const Crypto::PublicKey& spendPublicKey, const Crypto::SecretKey& spendSecretKey, uint64_t creationTimestamp) { auto& index = m_walletsContainer.get(); auto trackingMode = getTrackingMode(); @@ -513,6 +583,13 @@ void WalletGreen::addWallet(const Crypto::PublicKey& spendPublicKey, const Crypt trSubscription.addObserver(this); index.insert(insertIt, std::move(wallet)); + + if (index.size() == 1) { + m_synchronizer.subscribeConsumerNotifications(m_viewPublicKey, this); + getViewKeyKnownBlocks(m_viewPublicKey); + } + + return m_currency.accountAddressAsString({ spendPublicKey, m_viewPublicKey }); } void WalletGreen::deleteAddress(const std::string& address) { @@ -523,21 +600,32 @@ void WalletGreen::deleteAddress(const std::string& address) { auto it = m_walletsContainer.get().find(pubAddr.spendPublicKey); if (it == m_walletsContainer.get().end()) { - throw std::system_error(std::make_error_code(std::errc::invalid_argument)); + throw std::system_error(make_error_code(error::OBJECT_NOT_FOUND)); } - m_blockchainSynchronizer.stop(); + stopBlockchainSynchronizer(); m_actualBalance -= it->actualBalance; m_pendingBalance -= it->pendingBalance; m_synchronizer.removeSubscription(pubAddr); - m_spentOutputs.get().erase(&(*it)); + deleteContainerFromUnlockTransactionJobs(it->container); + std::vector deletedTransactions; + std::vector updatedTransactions = deleteTransfersForAddress(address, deletedTransactions); + deleteFromUncommitedTransactions(deletedTransactions); + m_walletsContainer.get().erase(it); if (m_walletsContainer.get().size() != 0) { - m_blockchainSynchronizer.start(); + startBlockchainSynchronizer(); + } else { + m_blockchain.clear(); + m_blockchain.push_back(m_currency.genesisBlockHash()); + } + + for (auto transactionId: updatedTransactions) { + pushEvent(makeTransactionUpdatedEvent(transactionId)); } } @@ -593,7 +681,7 @@ size_t WalletGreen::getTransactionTransferCount(size_t transactionIndex) const { throwIfNotInitialized(); throwIfStopped(); - auto bounds = getTransactionTransfers(transactionIndex); + auto bounds = getTransactionTransfersRange(transactionIndex); return static_cast(std::distance(bounds.first, bounds.second)); } @@ -601,18 +689,16 @@ WalletTransfer WalletGreen::getTransactionTransfer(size_t transactionIndex, size throwIfNotInitialized(); throwIfStopped(); - auto bounds = getTransactionTransfers(transactionIndex); + auto bounds = getTransactionTransfersRange(transactionIndex); if (transferIndex >= static_cast(std::distance(bounds.first, bounds.second))) { - throw std::system_error(std::make_error_code(std::errc::invalid_argument)); + throw std::system_error(make_error_code(std::errc::invalid_argument)); } return std::next(bounds.first, transferIndex)->second; } -std::pair WalletGreen::getTransactionTransfers( - size_t transactionIndex) const { - +WalletGreen::TransfersRange WalletGreen::getTransactionTransfersRange(size_t transactionIndex) const { auto val = std::make_pair(transactionIndex, WalletTransfer()); auto bounds = std::equal_range(m_transfers.begin(), m_transfers.end(), val, [] (const TransactionTransferPair& a, const TransactionTransferPair& b) { @@ -622,131 +708,37 @@ std::pair Wall return bounds; } -size_t WalletGreen::transfer(const WalletOrder& destination, - uint64_t fee, - uint64_t mixIn, - const std::string& extra, - uint64_t unlockTimestamp) { - - TransactionParameters transactionParameters; - transactionParameters.destinations.emplace_back(destination); - transactionParameters.fee = fee; - transactionParameters.mixIn = mixIn; - transactionParameters.extra = extra; - transactionParameters.unlockTimestamp = unlockTimestamp; - - return transfer(transactionParameters); -} - -size_t WalletGreen::transfer( - const std::vector& destinations, - uint64_t fee, - uint64_t mixIn, - const std::string& extra, - uint64_t unlockTimestamp) { - - TransactionParameters transactionParameters; - transactionParameters.destinations = destinations; - transactionParameters.fee = fee; - transactionParameters.mixIn = mixIn; - transactionParameters.extra = extra; - transactionParameters.unlockTimestamp = unlockTimestamp; - - return transfer(transactionParameters); -} - -size_t WalletGreen::transfer( - const std::string& sourceAddress, - const WalletOrder& destination, - uint64_t fee, - uint64_t mixIn, - std::string const& extra, - uint64_t unlockTimestamp) { - - TransactionParameters transactionParameters; - transactionParameters.sourceAddress = sourceAddress; - transactionParameters.destinations.emplace_back(destination); - transactionParameters.fee = fee; - transactionParameters.mixIn = mixIn; - transactionParameters.extra = extra; - transactionParameters.unlockTimestamp = unlockTimestamp; - - return transfer(transactionParameters); -} - -size_t WalletGreen::transfer( - const std::string& sourceAddress, - const std::vector& destinations, - uint64_t fee, - uint64_t mixIn, - const std::string& extra, - uint64_t unlockTimestamp) { - - TransactionParameters transactionParameters; - transactionParameters.sourceAddress = sourceAddress; - transactionParameters.destinations = destinations; - transactionParameters.fee = fee; - transactionParameters.mixIn = mixIn; - transactionParameters.extra = extra; - transactionParameters.unlockTimestamp = unlockTimestamp; - - return transfer(transactionParameters); -} - size_t WalletGreen::transfer(const TransactionParameters& transactionParameters) { + Tools::ScopeExit releaseContext([this] { + m_dispatcher.yield(); + }); + System::EventLock lk(m_readyEvent); throwIfNotInitialized(); throwIfTrackingMode(); throwIfStopped(); - std::vector wallets; - if (!transactionParameters.sourceAddress.empty()) { - WalletOuts wallet = pickWallet(transactionParameters.sourceAddress); - if (!wallet.outs.empty()) { - wallets.emplace_back(std::move(wallet)); - } - } else { - wallets = pickWalletsWithMoney(); - } - - return doTransfer(std::move(wallets), - transactionParameters.destinations, - transactionParameters.fee, - transactionParameters.mixIn, - transactionParameters.extra, - transactionParameters.unlockTimestamp, - transactionParameters.donation); + return doTransfer(transactionParameters); } -size_t WalletGreen::doTransfer(std::vector&& wallets, - const std::vector& orders, - uint64_t fee, - uint64_t mixIn, - const std::string& extra, - uint64_t unlockTimestamp, - const DonationSettings& donation) { - if (orders.empty()) { - throw std::system_error(make_error_code(error::ZERO_DESTINATION)); - } +void WalletGreen::prepareTransaction(std::vector&& wallets, + const std::vector& orders, + uint64_t fee, + uint64_t mixIn, + const std::string& extra, + uint64_t unlockTimestamp, + const DonationSettings& donation, + const CryptoNote::AccountPublicAddress& changeDestination, + PreparedTransaction& preparedTransaction) { - if (fee < m_currency.minimumFee()) { - throw std::system_error(make_error_code(error::FEE_TOO_SMALL)); - } - - if (donation.address.empty() ^ (donation.threshold == 0)) { - throw std::system_error(make_error_code(error::WRONG_PARAMETERS), "DonationSettings must have both address and threshold parameters filled"); - } - - std::vector destinations = convertOrdersToTransfer(orders); - validateAddresses(destinations, m_currency); - - uint64_t neededMoney = countNeededMoney(destinations, fee); + preparedTransaction.destinations = convertOrdersToTransfers(orders); + preparedTransaction.neededMoney = countNeededMoney(preparedTransaction.destinations, fee); std::vector selectedTransfers; - uint64_t foundMoney = selectTransfers(neededMoney, mixIn == 0, m_currency.defaultDustThreshold(), std::move(wallets), selectedTransfers); + uint64_t foundMoney = selectTransfers(preparedTransaction.neededMoney, mixIn == 0, m_currency.defaultDustThreshold(), std::move(wallets), selectedTransfers); - if (foundMoney < neededMoney) { + if (foundMoney < preparedTransaction.neededMoney) { throw std::system_error(make_error_code(error::WRONG_AMOUNT), "Not enough money"); } @@ -760,53 +752,197 @@ size_t WalletGreen::doTransfer(std::vector&& wallets, std::vector keysInfo; prepareInputs(selectedTransfers, mixinResult, mixIn, keysInfo); - uint64_t donationAmount = pushDonationTransferIfPossible(donation, foundMoney - neededMoney, m_currency.defaultDustThreshold(), destinations); - uint64_t changeAmount = foundMoney - neededMoney - donationAmount; + uint64_t donationAmount = pushDonationTransferIfPossible(donation, foundMoney - preparedTransaction.neededMoney, m_currency.defaultDustThreshold(), preparedTransaction.destinations); + preparedTransaction.changeAmount = foundMoney - preparedTransaction.neededMoney - donationAmount; - std::vector decomposedOutputs = splitDestinations(destinations, m_currency.defaultDustThreshold(), m_currency); - if (changeAmount != 0) { - CryptoNote::AccountPublicAddress changeDestination = { m_walletsContainer.get()[0].spendPublicKey, m_viewPublicKey }; - auto splittedChange = splitAmount(changeAmount, changeDestination, m_currency.defaultDustThreshold()); + std::vector decomposedOutputs = splitDestinations(preparedTransaction.destinations, m_currency.defaultDustThreshold(), m_currency); + if (preparedTransaction.changeAmount != 0) { + WalletTransfer changeTransfer; + changeTransfer.type = WalletTransferType::CHANGE; + changeTransfer.address = m_currency.accountAddressAsString(changeDestination); + changeTransfer.amount = static_cast(preparedTransaction.changeAmount); + preparedTransaction.destinations.emplace_back(std::move(changeTransfer)); + + auto splittedChange = splitAmount(preparedTransaction.changeAmount, changeDestination, m_currency.defaultDustThreshold()); decomposedOutputs.emplace_back(std::move(splittedChange)); } - std::unique_ptr tx = makeTransaction(decomposedOutputs, keysInfo, extra, unlockTimestamp); + preparedTransaction.transaction = makeTransaction(decomposedOutputs, keysInfo, extra, unlockTimestamp); +} - size_t txId = insertOutgoingTransactionAndPushEvent(tx->getTransactionHash(), -static_cast(neededMoney), fee, tx->getExtra(), unlockTimestamp); - pushBackOutgoingTransfers(txId, destinations); - m_fusionTxsCache.emplace(txId, false); - - markOutputsSpent(tx->getTransactionHash(), selectedTransfers); - - try { - sendTransaction(tx.get()); - } catch (std::exception&) { - updateTransactionStateAndPushEvent(txId, WalletTransactionState::FAILED); - deleteSpentOutputs(tx->getTransactionHash()); - throw; +void WalletGreen::validateTransactionParameters(const TransactionParameters& transactionParameters) { + if (transactionParameters.destinations.empty()) { + throw std::system_error(make_error_code(error::ZERO_DESTINATION)); } - m_change[tx->getTransactionHash()] = changeAmount; - updateTransactionStateAndPushEvent(txId, WalletTransactionState::SUCCEEDED); - updateUsedWalletsBalances(selectedTransfers); + if (transactionParameters.fee < m_currency.minimumFee()) { + throw std::system_error(make_error_code(error::FEE_TOO_SMALL)); + } - return txId; + if (transactionParameters.donation.address.empty() != (transactionParameters.donation.threshold == 0)) { + throw std::system_error(make_error_code(error::WRONG_PARAMETERS), "DonationSettings must have both address and threshold parameters filled"); + } + + validateAddresses(transactionParameters.sourceAddresses, m_currency); + + auto badAddr = std::find_if(transactionParameters.sourceAddresses.begin(), transactionParameters.sourceAddresses.end(), [this](const std::string& addr) { + return !isMyAddress(addr); + }); + if (badAddr != transactionParameters.sourceAddresses.end()) { + throw std::system_error(make_error_code(error::BAD_ADDRESS), "Source address must belong to current container: " + *badAddr); + } + + validateOrders(transactionParameters.destinations, m_currency); + + if (transactionParameters.changeDestination.empty()) { + if (transactionParameters.sourceAddresses.size() > 1) { + throw std::system_error(make_error_code(error::CHANGE_ADDRESS_REQUIRED), "Set change destination address"); + } else if (transactionParameters.sourceAddresses.empty() && m_walletsContainer.size() > 1) { + throw std::system_error(make_error_code(error::CHANGE_ADDRESS_REQUIRED), "Set change destination address"); + } + } else { + if (!CryptoNote::validateAddress(transactionParameters.changeDestination, m_currency)) { + throw std::system_error(make_error_code(CryptoNote::error::BAD_ADDRESS), "Wrong change address"); + } + + if (!isMyAddress(transactionParameters.changeDestination)) { + throw std::system_error(make_error_code(error::CHANGE_ADDRESS_NOT_FOUND), "Change destination address not found in current container"); + } + } +} + +size_t WalletGreen::doTransfer(const TransactionParameters& transactionParameters) { + validateTransactionParameters(transactionParameters); + CryptoNote::AccountPublicAddress changeDestination = getChangeDestination(transactionParameters.changeDestination, transactionParameters.sourceAddresses); + + std::vector wallets; + if (!transactionParameters.sourceAddresses.empty()) { + wallets = pickWallets(transactionParameters.sourceAddresses); + } else { + wallets = pickWalletsWithMoney(); + } + + PreparedTransaction preparedTransaction; + prepareTransaction(std::move(wallets), + transactionParameters.destinations, + transactionParameters.fee, + transactionParameters.mixIn, + transactionParameters.extra, + transactionParameters.unlockTimestamp, + transactionParameters.donation, + changeDestination, + preparedTransaction); + + return validateSaveAndSendTransaction(*preparedTransaction.transaction, preparedTransaction.destinations, false, true); +} + +size_t WalletGreen::makeTransaction(const TransactionParameters& sendingTransaction) { + throwIfNotInitialized(); + throwIfTrackingMode(); + throwIfStopped(); + + Tools::ScopeExit releaseContext([this] { + m_dispatcher.yield(); + }); + + System::EventLock lk(m_readyEvent); + + validateTransactionParameters(sendingTransaction); + CryptoNote::AccountPublicAddress changeDestination = getChangeDestination(sendingTransaction.changeDestination, sendingTransaction.sourceAddresses); + + std::vector wallets; + if (!sendingTransaction.sourceAddresses.empty()) { + wallets = pickWallets(sendingTransaction.sourceAddresses); + } else { + wallets = pickWalletsWithMoney(); + } + + PreparedTransaction preparedTransaction; + prepareTransaction( + std::move(wallets), + sendingTransaction.destinations, + sendingTransaction.fee, + sendingTransaction.mixIn, + sendingTransaction.extra, + sendingTransaction.unlockTimestamp, + sendingTransaction.donation, + changeDestination, + preparedTransaction); + + return validateSaveAndSendTransaction(*preparedTransaction.transaction, preparedTransaction.destinations, false, false); +} + +void WalletGreen::commitTransaction(size_t transactionId) { + System::EventLock lk(m_readyEvent); + + throwIfNotInitialized(); + throwIfStopped(); + throwIfTrackingMode(); + + if (transactionId >= m_transactions.size()) { + throw std::system_error(make_error_code(CryptoNote::error::INDEX_OUT_OF_RANGE)); + } + + auto txIt = std::next(m_transactions.get().begin(), transactionId); + if (m_uncommitedTransactions.count(transactionId) == 0 || txIt->state != WalletTransactionState::CREATED) { + throw std::system_error(make_error_code(error::TX_TRANSFER_IMPOSSIBLE)); + } + + System::Event completion(m_dispatcher); + std::error_code ec; + + m_node.relayTransaction(m_uncommitedTransactions[transactionId], [&ec, &completion, this](std::error_code error) { + ec = error; + this->m_dispatcher.remoteSpawn(std::bind(asyncRequestCompletion, std::ref(completion))); + }); + completion.wait(); + + if (!ec) { + updateTransactionStateAndPushEvent(transactionId, WalletTransactionState::SUCCEEDED); + m_uncommitedTransactions.erase(transactionId); + } else { + throw std::system_error(ec); + } +} + +void WalletGreen::rollbackUncommitedTransaction(size_t transactionId) { + Tools::ScopeExit releaseContext([this] { + m_dispatcher.yield(); + }); + + System::EventLock lk(m_readyEvent); + + throwIfNotInitialized(); + throwIfStopped(); + throwIfTrackingMode(); + + if (transactionId >= m_transactions.size()) { + throw std::system_error(make_error_code(CryptoNote::error::INDEX_OUT_OF_RANGE)); + } + + auto txIt = m_transactions.get().begin(); + std::advance(txIt, transactionId); + if (m_uncommitedTransactions.count(transactionId) == 0 || txIt->state != WalletTransactionState::CREATED) { + throw std::system_error(make_error_code(error::TX_CANCEL_IMPOSSIBLE)); + } + + removeUnconfirmedTransaction(getObjectHash(m_uncommitedTransactions[transactionId])); + m_uncommitedTransactions.erase(transactionId); } void WalletGreen::pushBackOutgoingTransfers(size_t txId, const std::vector& destinations) { + for (const auto& dest: destinations) { WalletTransfer d; d.type = dest.type; d.address = dest.address; - d.amount = -dest.amount; + d.amount = dest.amount; - m_transfers.push_back(std::make_pair(txId, d)); + m_transfers.emplace_back(txId, std::move(d)); } } -size_t WalletGreen::insertOutgoingTransactionAndPushEvent(const Hash& transactionHash, - int64_t totalAmount, uint64_t fee, const BinaryArray& extra, uint64_t unlockTimestamp) { - +size_t WalletGreen::insertOutgoingTransactionAndPushEvent(const Hash& transactionHash, uint64_t fee, const BinaryArray& extra, uint64_t unlockTimestamp) { WalletTransaction insertTx; insertTx.state = WalletTransactionState::CREATED; insertTx.creationTime = static_cast(time(nullptr)); @@ -815,7 +951,7 @@ size_t WalletGreen::insertOutgoingTransactionAndPushEvent(const Hash& transactio insertTx.extra.assign(reinterpret_cast(extra.data()), extra.size()); insertTx.fee = fee; insertTx.hash = transactionHash; - insertTx.totalAmount = totalAmount; + insertTx.totalAmount = 0; // 0 until transactionHandlingEnd() is called insertTx.timestamp = 0; //0 until included in a block insertTx.isBase = false; @@ -839,13 +975,13 @@ void WalletGreen::updateTransactionStateAndPushEvent(size_t transactionId, Walle } } -void WalletGreen::updateWalletTransactionInfoAndPushEvent(size_t transactionId, const CryptoNote::TransactionInformation& info, bool transfersUpdated) { +bool WalletGreen::updateWalletTransactionInfo(size_t transactionId, const CryptoNote::TransactionInformation& info, int64_t totalAmount) { auto& txIdIndex = m_transactions.get(); assert(transactionId < txIdIndex.size()); auto it = std::next(txIdIndex.begin(), transactionId); bool updated = false; - bool r = txIdIndex.modify(it, [&info, &updated](WalletTransaction& transaction) { + bool r = txIdIndex.modify(it, [&info, totalAmount, &updated](WalletTransaction& transaction) { if (transaction.blockHeight != info.blockHeight) { transaction.blockHeight = info.blockHeight; updated = true; @@ -856,12 +992,21 @@ void WalletGreen::updateWalletTransactionInfoAndPushEvent(size_t transactionId, updated = true; } - if (transaction.state != WalletTransactionState::SUCCEEDED) { + bool isSucceeded = transaction.state == WalletTransactionState::SUCCEEDED; + // If transaction was sent to daemon, it can not have CREATED and FAILED states, its state can be SUCCEEDED, CANCELLED or DELETED + bool wasSent = transaction.state != WalletTransactionState::CREATED && transaction.state != WalletTransactionState::FAILED; + bool isConfirmed = transaction.blockHeight != WALLET_UNCONFIRMED_TRANSACTION_HEIGHT; + if (!isSucceeded && (wasSent || isConfirmed)) { //transaction may be deleted first then added again transaction.state = WalletTransactionState::SUCCEEDED; updated = true; } + if (transaction.totalAmount != totalAmount) { + transaction.totalAmount = totalAmount; + updated = true; + } + // Fix LegacyWallet error. Some old versions didn't fill extra field if (transaction.extra.empty() && !info.extra.empty()) { transaction.extra = Common::asString(info.extra); @@ -877,12 +1022,10 @@ void WalletGreen::updateWalletTransactionInfoAndPushEvent(size_t transactionId, assert(r); - if (updated || transfersUpdated) { - pushEvent(makeTransactionUpdatedEvent(transactionId)); - } + return updated; } -size_t WalletGreen::insertBlockchainTransactionAndPushEvent(const TransactionInformation& info, int64_t txBalance) { +size_t WalletGreen::insertBlockchainTransaction(const TransactionInformation& info, int64_t txBalance) { auto& index = m_transactions.get(); WalletTransaction tx; @@ -890,28 +1033,218 @@ size_t WalletGreen::insertBlockchainTransactionAndPushEvent(const TransactionInf tx.timestamp = info.timestamp; tx.blockHeight = info.blockHeight; tx.hash = info.transactionHash; - tx.fee = info.totalAmountIn - info.totalAmountOut; + tx.isBase = info.totalAmountIn == 0; + if (tx.isBase) { + tx.fee = 0; + } else { + tx.fee = info.totalAmountIn - info.totalAmountOut; + } + tx.unlockTime = info.unlockTime; tx.extra.assign(reinterpret_cast(info.extra.data()), info.extra.size()); tx.totalAmount = txBalance; tx.creationTime = info.timestamp; - tx.isBase = info.totalAmountIn == 0; size_t txId = index.size(); index.push_back(std::move(tx)); - pushEvent(makeTransactionCreatedEvent(txId)); - return txId; } -void WalletGreen::insertIncomingTransfer(size_t txId, const std::string& address, int64_t amount) { - auto it = std::upper_bound(m_transfers.begin(), m_transfers.end(), txId, [] (size_t val, const TransactionTransferPair& a) { - return val < a.first; +bool WalletGreen::updateTransactionTransfers(size_t transactionId, const std::vector& containerAmountsList, + int64_t allInputsAmount, int64_t allOutputsAmount) { + + assert(allInputsAmount <= 0); + assert(allOutputsAmount >= 0); + + bool updated = false; + + auto transfersRange = getTransactionTransfersRange(transactionId); + // Iterators can be invalidated, so the first transfer is addressed by its index + size_t firstTransferIdx = std::distance(m_transfers.cbegin(), transfersRange.first); + + TransfersMap initialTransfers = getKnownTransfersMap(transactionId, firstTransferIdx); + + std::unordered_set myInputAddresses; + std::unordered_set myOutputAddresses; + int64_t myInputsAmount = 0; + int64_t myOutputsAmount = 0; + for (auto containerAmount : containerAmountsList) { + AccountPublicAddress address{ getWalletRecord(containerAmount.container).spendPublicKey, m_viewPublicKey }; + std::string addressString = m_currency.accountAddressAsString(address); + + updated |= updateAddressTransfers(transactionId, firstTransferIdx, addressString, initialTransfers[addressString].input, containerAmount.amounts.input); + updated |= updateAddressTransfers(transactionId, firstTransferIdx, addressString, initialTransfers[addressString].output, containerAmount.amounts.output); + + myInputsAmount += containerAmount.amounts.input; + myOutputsAmount += containerAmount.amounts.output; + + if (containerAmount.amounts.input != 0) { + myInputAddresses.emplace(addressString); + } + + if (containerAmount.amounts.output != 0) { + myOutputAddresses.emplace(addressString); + } + } + + assert(myInputsAmount >= allInputsAmount); + assert(myOutputsAmount <= allOutputsAmount); + + int64_t knownInputsAmount = 0; + int64_t knownOutputsAmount = 0; + auto updatedTransfers = getKnownTransfersMap(transactionId, firstTransferIdx); + for (const auto& pair : updatedTransfers) { + knownInputsAmount += pair.second.input; + knownOutputsAmount += pair.second.output; + } + + assert(myInputsAmount >= knownInputsAmount); + assert(myOutputsAmount <= knownOutputsAmount); + + updated |= updateUnknownTransfers(transactionId, firstTransferIdx, myInputAddresses, knownInputsAmount, myInputsAmount, allInputsAmount, false); + updated |= updateUnknownTransfers(transactionId, firstTransferIdx, myOutputAddresses, knownOutputsAmount, myOutputsAmount, allOutputsAmount, true); + + return updated; +} + +WalletGreen::TransfersMap WalletGreen::getKnownTransfersMap(size_t transactionId, size_t firstTransferIdx) const { + TransfersMap result; + + for (auto it = std::next(m_transfers.begin(), firstTransferIdx); it != m_transfers.end() && it->first == transactionId; ++it) { + const auto& address = it->second.address; + + if (!address.empty()) { + if (it->second.amount < 0) { + result[address].input += it->second.amount; + } else { + assert(it->second.amount > 0); + result[address].output += it->second.amount; + } + } + } + + return result; +} + +bool WalletGreen::updateAddressTransfers(size_t transactionId, size_t firstTransferIdx, const std::string& address, int64_t knownAmount, int64_t targetAmount) { + assert((knownAmount > 0 && targetAmount > 0) || (knownAmount < 0 && targetAmount < 0) || knownAmount == 0 || targetAmount == 0); + + bool updated = false; + + if (knownAmount != targetAmount) { + if (knownAmount == 0) { + appendTransfer(transactionId, firstTransferIdx, address, targetAmount); + updated = true; + } else if (targetAmount == 0) { + assert(knownAmount != 0); + updated |= eraseTransfersByAddress(transactionId, firstTransferIdx, address, knownAmount > 0); + } else { + updated |= adjustTransfer(transactionId, firstTransferIdx, address, targetAmount); + } + } + + return updated; +} + +bool WalletGreen::updateUnknownTransfers(size_t transactionId, size_t firstTransferIdx, const std::unordered_set& myAddresses, + int64_t knownAmount, int64_t myAmount, int64_t totalAmount, bool isOutput) { + + bool updated = false; + + if (std::abs(knownAmount) > std::abs(totalAmount)) { + updated |= eraseForeignTransfers(transactionId, firstTransferIdx, myAddresses, isOutput); + if (totalAmount == myAmount) { + updated |= eraseTransfersByAddress(transactionId, firstTransferIdx, std::string(), isOutput); + } else { + assert(std::abs(totalAmount) > std::abs(myAmount)); + updated |= adjustTransfer(transactionId, firstTransferIdx, std::string(), totalAmount - myAmount); + } + } else if (knownAmount == totalAmount) { + updated |= eraseTransfersByAddress(transactionId, firstTransferIdx, std::string(), isOutput); + } else { + assert(std::abs(totalAmount) > std::abs(knownAmount)); + updated |= adjustTransfer(transactionId, firstTransferIdx, std::string(), totalAmount - knownAmount); + } + + return updated; +} + +void WalletGreen::appendTransfer(size_t transactionId, size_t firstTransferIdx, const std::string& address, int64_t amount) { + auto it = std::next(m_transfers.begin(), firstTransferIdx); + auto insertIt = std::upper_bound(it, m_transfers.end(), transactionId, [](size_t transactionId, const TransactionTransferPair& pair) { + return transactionId < pair.first; }); - WalletTransfer tr { WalletTransferType::USUAL, address, amount }; - m_transfers.insert(it, std::make_pair(txId, std::move(tr))); + WalletTransfer transfer{ WalletTransferType::USUAL, address, amount }; + m_transfers.emplace(insertIt, std::piecewise_construct, std::forward_as_tuple(transactionId), std::forward_as_tuple(transfer)); +} + +bool WalletGreen::adjustTransfer(size_t transactionId, size_t firstTransferIdx, const std::string& address, int64_t amount) { + assert(amount != 0); + + bool updated = false; + bool updateOutputTransfers = amount > 0; + bool firstAddressTransferFound = false; + auto it = std::next(m_transfers.begin(), firstTransferIdx); + while (it != m_transfers.end() && it->first == transactionId) { + assert(it->second.amount != 0); + bool transferIsOutput = it->second.amount > 0; + if (transferIsOutput == updateOutputTransfers && it->second.address == address) { + if (firstAddressTransferFound) { + it = m_transfers.erase(it); + updated = true; + } else { + if (it->second.amount != amount) { + it->second.amount = amount; + updated = true; + } + + firstAddressTransferFound = true; + ++it; + } + } else { + ++it; + } + } + + if (!firstAddressTransferFound) { + WalletTransfer transfer{ WalletTransferType::USUAL, address, amount }; + m_transfers.emplace(it, std::piecewise_construct, std::forward_as_tuple(transactionId), std::forward_as_tuple(transfer)); + updated = true; + } + + return updated; +} + +bool WalletGreen::eraseTransfers(size_t transactionId, size_t firstTransferIdx, std::function&& predicate) { + bool erased = false; + auto it = std::next(m_transfers.begin(), firstTransferIdx); + while (it != m_transfers.end() && it->first == transactionId) { + bool transferIsOutput = it->second.amount > 0; + if (predicate(transferIsOutput, it->second.address)) { + it = m_transfers.erase(it); + erased = true; + } else { + ++it; + } + } + + return erased; +} + +bool WalletGreen::eraseTransfersByAddress(size_t transactionId, size_t firstTransferIdx, const std::string& address, bool eraseOutputTransfers) { + return eraseTransfers(transactionId, firstTransferIdx, [&address, eraseOutputTransfers](bool isOutput, const std::string& transferAddress) { + return eraseOutputTransfers == isOutput && address == transferAddress; + }); +} + +bool WalletGreen::eraseForeignTransfers(size_t transactionId, size_t firstTransferIdx, const std::unordered_set& knownAddresses, + bool eraseOutputTransfers) { + + return eraseTransfers(transactionId, firstTransferIdx, [this, &knownAddresses, eraseOutputTransfers](bool isOutput, const std::string& transferAddress) { + return eraseOutputTransfers == isOutput && knownAddresses.count(transferAddress) == 0; + }); } std::unique_ptr WalletGreen::makeTransaction(const std::vector& decomposedOutputs, @@ -951,23 +1284,12 @@ std::unique_ptr WalletGreen::makeTransaction(const std return tx; } -void WalletGreen::sendTransaction(ITransaction* tx) { +void WalletGreen::sendTransaction(const CryptoNote::Transaction& cryptoNoteTransaction) { System::Event completion(m_dispatcher); std::error_code ec; - CryptoNote::Transaction oldTxFormat; - - const auto& ba = tx->getTransactionData(); - - if (ba.size() > m_upperTransactionSizeLimit) { - throw std::system_error(make_error_code(error::TRANSACTION_SIZE_TOO_BIG)); - } - - if (!fromBinaryArray(oldTxFormat, ba)) { - throw std::system_error(make_error_code(error::INTERNAL_WALLET_ERROR)); - } throwIfStopped(); - m_node.relayTransaction(oldTxFormat, [&ec, &completion, this] (std::error_code error) { + m_node.relayTransaction(cryptoNoteTransaction, [&ec, &completion, this](std::error_code error) { ec = error; this->m_dispatcher.remoteSpawn(std::bind(asyncRequestCompletion, std::ref(completion))); }); @@ -978,6 +1300,51 @@ void WalletGreen::sendTransaction(ITransaction* tx) { } } +size_t WalletGreen::validateSaveAndSendTransaction(const ITransactionReader& transaction, const std::vector& destinations, bool isFusion, bool send) { + BinaryArray transactionData = transaction.getTransactionData(); + + if (transactionData.size() > m_upperTransactionSizeLimit) { + throw std::system_error(make_error_code(error::TRANSACTION_SIZE_TOO_BIG)); + } + + CryptoNote::Transaction cryptoNoteTransaction; + if (!fromBinaryArray(cryptoNoteTransaction, transactionData)) { + throw std::system_error(make_error_code(error::INTERNAL_WALLET_ERROR), "Failed to deserialize created transaction"); + } + + uint64_t fee = transaction.getInputTotalAmount() - transaction.getOutputTotalAmount(); + size_t transactionId = insertOutgoingTransactionAndPushEvent(transaction.getTransactionHash(), fee, transaction.getExtra(), transaction.getUnlockTime()); + Tools::ScopeExit rollbackTransactionInsertion([this, transactionId] { + updateTransactionStateAndPushEvent(transactionId, WalletTransactionState::FAILED); + }); + + m_fusionTxsCache.emplace(transactionId, isFusion); + pushBackOutgoingTransfers(transactionId, destinations); + + addUnconfirmedTransaction(transaction); + Tools::ScopeExit rollbackAddingUnconfirmedTransaction([this, &transaction] { + try { + removeUnconfirmedTransaction(transaction.getTransactionHash()); + } catch (...) { + // Ignore any exceptions. If rollback fails then the transaction is stored as unconfirmed and will be deleted after wallet relaunch + // during transaction pool synchronization + } + }); + + if (send) { + sendTransaction(cryptoNoteTransaction); + updateTransactionStateAndPushEvent(transactionId, WalletTransactionState::SUCCEEDED); + } else { + assert(m_uncommitedTransactions.count(transactionId) == 0); + m_uncommitedTransactions.emplace(transactionId, std::move(cryptoNoteTransaction)); + } + + rollbackAddingUnconfirmedTransaction.cancel(); + rollbackTransactionInsertion.cancel(); + + return transactionId; +} + AccountKeys WalletGreen::makeAccountKeys(const WalletRecord& wallet) const { AccountKeys keys; keys.address.spendPublicKey = wallet.spendPublicKey; @@ -1040,7 +1407,7 @@ uint64_t WalletGreen::selectTransfers( size_t outIndex = outDistribution(randomGenerator); TransactionOutputInformation out = addressOuts[outIndex]; - if (!isOutputUsed(out) && (out.amount > dustThreshold || dust)) { + if (out.amount > dustThreshold || dust) { if (out.amount <= dustThreshold) { dust = false; } @@ -1061,11 +1428,9 @@ uint64_t WalletGreen::selectTransfers( } for (const auto& addressOuts : walletOuts) { - auto it = std::find_if(addressOuts.outs.begin(), addressOuts.outs.end(), - [dustThreshold, this] (const TransactionOutputInformation& out) { - return out.amount <= dustThreshold && (!this->isOutputUsed(out)); - } - ); + auto it = std::find_if(addressOuts.outs.begin(), addressOuts.outs.end(), [dustThreshold] (const TransactionOutputInformation& out) { + return out.amount <= dustThreshold; + }); if (it != addressOuts.outs.end()) { foundMoney += it->amount; @@ -1109,6 +1474,20 @@ WalletGreen::WalletOuts WalletGreen::pickWallet(const std::string& address) { return outs; } +std::vector WalletGreen::pickWallets(const std::vector& addresses) { + std::vector wallets; + wallets.reserve(addresses.size()); + + for (const auto& address: addresses) { + WalletOuts wallet = pickWallet(address); + if (!wallet.outs.empty()) { + wallets.emplace_back(std::move(wallet)); + } + } + + return wallets; +} + std::vector WalletGreen::splitDestinations(const std::vector& destinations, uint64_t dustThreshold, const CryptoNote::Currency& currency) { @@ -1190,6 +1569,107 @@ void WalletGreen::prepareInputs( } } +WalletTransactionWithTransfers WalletGreen::getTransaction(const Crypto::Hash& transactionHash) const { + throwIfNotInitialized(); + throwIfStopped(); + + auto& hashIndex = m_transactions.get(); + auto it = hashIndex.find(transactionHash); + if (it == hashIndex.end()) { + throw std::system_error(make_error_code(error::OBJECT_NOT_FOUND), "Transaction not found"); + } + + WalletTransactionWithTransfers walletTransaction; + walletTransaction.transaction = *it; + walletTransaction.transfers = getTransactionTransfers(*it); + + return walletTransaction; +} + +std::vector WalletGreen::getTransactions(const Crypto::Hash& blockHash, size_t count) const { + throwIfNotInitialized(); + throwIfStopped(); + + auto& hashIndex = m_blockchain.get(); + auto it = hashIndex.find(blockHash); + if (it == hashIndex.end()) { + return std::vector(); + } + + auto heightIt = m_blockchain.project(it); + + uint32_t blockIndex = static_cast(std::distance(m_blockchain.get().begin(), heightIt)); + return getTransactionsInBlocks(blockIndex, count); +} + +std::vector WalletGreen::getTransactions(uint32_t blockIndex, size_t count) const { + throwIfNotInitialized(); + throwIfStopped(); + + return getTransactionsInBlocks(blockIndex, count); +} + +std::vector WalletGreen::getBlockHashes(uint32_t blockIndex, size_t count) const { + throwIfNotInitialized(); + throwIfStopped(); + + auto& index = m_blockchain.get(); + + if (blockIndex >= index.size()) { + return std::vector(); + } + + auto start = std::next(index.begin(), blockIndex); + auto end = std::next(index.begin(), std::min(index.size(), blockIndex + count)); + return std::vector(start, end); +} + +uint32_t WalletGreen::getBlockCount() const { + throwIfNotInitialized(); + throwIfStopped(); + + uint32_t blockCount = static_cast(m_blockchain.size()); + assert(blockCount != 0); + + return blockCount; +} + +std::vector WalletGreen::getUnconfirmedTransactions() const { + throwIfNotInitialized(); + throwIfStopped(); + + std::vector result; + auto lowerBound = m_transactions.get().lower_bound(WALLET_UNCONFIRMED_TRANSACTION_HEIGHT); + for (auto it = lowerBound; it != m_transactions.get().end(); ++it) { + if (it->state != WalletTransactionState::SUCCEEDED) { + continue; + } + + WalletTransactionWithTransfers transaction; + transaction.transaction = *it; + transaction.transfers = getTransactionTransfers(*it); + + result.push_back(transaction); + } + + return result; +} + +std::vector WalletGreen::getDelayedTransactionIds() const { + throwIfNotInitialized(); + throwIfStopped(); + throwIfTrackingMode(); + + std::vector result; + result.reserve(m_uncommitedTransactions.size()); + + for (const auto& kv: m_uncommitedTransactions) { + result.push_back(kv.first); + } + + return result; +} + void WalletGreen::start() { m_stopped = false; } @@ -1257,6 +1737,51 @@ void WalletGreen::onSynchronizationCompleted() { pushEvent(makeSyncCompletedEvent()); } +void WalletGreen::onBlocksAdded(const Crypto::PublicKey& viewPublicKey, const std::vector& blockHashes) { + m_dispatcher.remoteSpawn([this, blockHashes] () { blocksAdded(blockHashes); } ); +} + +void WalletGreen::blocksAdded(const std::vector& blockHashes) { + System::EventLock lk(m_readyEvent); + + if (m_state == WalletState::NOT_INITIALIZED) { + return; + } + + m_blockchain.insert(m_blockchain.end(), blockHashes.begin(), blockHashes.end()); +} + +void WalletGreen::onBlockchainDetach(const Crypto::PublicKey& viewPublicKey, uint32_t blockIndex) { + m_dispatcher.remoteSpawn([this, blockIndex] () { blocksRollback(blockIndex); } ); +} + +void WalletGreen::blocksRollback(uint32_t blockIndex) { + System::EventLock lk(m_readyEvent); + + if (m_state == WalletState::NOT_INITIALIZED) { + return; + } + + auto& blockHeightIndex = m_blockchain.get(); + blockHeightIndex.erase(std::next(blockHeightIndex.begin(), blockIndex), blockHeightIndex.end()); +} + +void WalletGreen::onTransactionDeleteBegin(const Crypto::PublicKey& viewPublicKey, Crypto::Hash transactionHash) { + m_dispatcher.remoteSpawn([=]() { transactionDeleteBegin(transactionHash); }); +} + +// TODO remove +void WalletGreen::transactionDeleteBegin(Crypto::Hash /*transactionHash*/) { +} + +void WalletGreen::onTransactionDeleteEnd(const Crypto::PublicKey& viewPublicKey, Crypto::Hash transactionHash) { + m_dispatcher.remoteSpawn([=]() { transactionDeleteEnd(transactionHash); }); +} + +// TODO remove +void WalletGreen::transactionDeleteEnd(Crypto::Hash transactionHash) { +} + void WalletGreen::unlockBalances(uint32_t height) { auto& index = m_unlockTransactionsJob.get(); auto upper = index.upper_bound(height); @@ -1271,52 +1796,83 @@ void WalletGreen::unlockBalances(uint32_t height) { } } -void WalletGreen::onTransactionUpdated(ITransfersSubscription* object, const Hash& transactionHash) { - m_dispatcher.remoteSpawn([object, transactionHash, this] () { this->transactionUpdated(object, transactionHash); } ); +void WalletGreen::onTransactionUpdated(ITransfersSubscription* /*object*/, const Crypto::Hash& /*transactionHash*/) { + // Deprecated, ignore it. New event handler is onTransactionUpdated(const Crypto::PublicKey&, const Crypto::Hash&, const std::vector&) } -void WalletGreen::transactionUpdated(ITransfersSubscription* object, const Hash& transactionHash) { +void WalletGreen::onTransactionUpdated(const Crypto::PublicKey&, const Crypto::Hash& transactionHash, const std::vector& containers) { + assert(!containers.empty()); + + TransactionInformation info; + std::vector containerAmountsList; + containerAmountsList.reserve(containers.size()); + for (auto container : containers) { + uint64_t inputsAmount; + // Don't move this code to the following remote spawn, because it guarantees that the container has the transaction + uint64_t outputsAmount; + bool found = container->getTransactionInformation(transactionHash, info, &inputsAmount, &outputsAmount); + assert(found); + + ContainerAmounts containerAmounts; + containerAmounts.container = container; + containerAmounts.amounts.input = -static_cast(inputsAmount); + containerAmounts.amounts.output = static_cast(outputsAmount); + containerAmountsList.emplace_back(std::move(containerAmounts)); + } + + m_dispatcher.remoteSpawn([this, info, containerAmountsList] { + this->transactionUpdated(info, containerAmountsList); + }); +} + +void WalletGreen::transactionUpdated(const TransactionInformation& transactionInfo, const std::vector& containerAmountsList) { System::EventLock lk(m_readyEvent); if (m_state == WalletState::NOT_INITIALIZED) { return; } - CryptoNote::ITransfersContainer* container = &object->getContainer(); + bool updated = false; + bool isNew = false; - deleteSpentOutputs(transactionHash); - - CryptoNote::TransactionInformation info; - int64_t txBalance; - bool found = container->getTransactionInformation(transactionHash, info, txBalance); - assert(found); - bool addTransfer = txBalance > 0; - - auto& hashIndex = m_transactions.get(); - auto it = hashIndex.find(info.transactionHash); + int64_t totalAmount = std::accumulate(containerAmountsList.begin(), containerAmountsList.end(), static_cast(0), + [](int64_t sum, const ContainerAmounts& containerAmounts) { return sum + containerAmounts.amounts.input + containerAmounts.amounts.output; }); size_t transactionId; + auto& hashIndex = m_transactions.get(); + auto it = hashIndex.find(transactionInfo.transactionHash); if (it != hashIndex.end()) { transactionId = std::distance(m_transactions.get().begin(), m_transactions.project(it)); - updateWalletTransactionInfoAndPushEvent(transactionId, info, addTransfer); + updated |= updateWalletTransactionInfo(transactionId, transactionInfo, totalAmount); } else { - transactionId = insertBlockchainTransactionAndPushEvent(info, txBalance); - m_fusionTxsCache.emplace(transactionId, isFusionTransaction(m_transactions.get()[transactionId])); + isNew = true; + transactionId = insertBlockchainTransaction(transactionInfo, totalAmount); + m_fusionTxsCache.emplace(transactionId, isFusionTransaction(*it)); } - if (addTransfer) { - AccountPublicAddress walletAddress{ getWalletRecord(container).spendPublicKey, m_viewPublicKey }; - insertIncomingTransfer(transactionId, m_currency.accountAddressAsString(walletAddress), txBalance); + if (transactionInfo.blockHeight != CryptoNote::WALLET_UNCONFIRMED_TRANSACTION_HEIGHT) { + // In some cases a transaction can be included to a block but not removed from m_uncommitedTransactions. Fix it + m_uncommitedTransactions.erase(transactionId); } - m_change.erase(transactionHash); + // Update cached balance + for (auto containerAmounts : containerAmountsList) { + updateBalance(containerAmounts.container); - if (info.blockHeight != CryptoNote::WALLET_UNCONFIRMED_TRANSACTION_HEIGHT) { - uint32_t unlockHeight = std::max(info.blockHeight + m_transactionSoftLockTime, static_cast(info.unlockTime)); - insertUnlockTransactionJob(transactionHash, unlockHeight, container); + if (transactionInfo.blockHeight != CryptoNote::WALLET_UNCONFIRMED_TRANSACTION_HEIGHT) { + uint32_t unlockHeight = std::max(transactionInfo.blockHeight + m_transactionSoftLockTime, static_cast(transactionInfo.unlockTime)); + insertUnlockTransactionJob(transactionInfo.transactionHash, unlockHeight, containerAmounts.container); + } } - updateBalance(container); + updated |= updateTransactionTransfers(transactionId, containerAmountsList, -static_cast(transactionInfo.totalAmountIn), + static_cast(transactionInfo.totalAmountOut)); + + if (isNew) { + pushEvent(makeTransactionCreatedEvent(transactionId)); + } else if (updated) { + pushEvent(makeTransactionUpdatedEvent(transactionId)); + } } void WalletGreen::pushEvent(const WalletEvent& event) { @@ -1328,7 +1884,7 @@ size_t WalletGreen::getTransactionId(const Hash& transactionHash) const { auto it = m_transactions.get().find(transactionHash); if (it == m_transactions.get().end()) { - throw std::system_error(std::make_error_code(std::errc::invalid_argument)); + throw std::system_error(make_error_code(std::errc::invalid_argument)); } auto rndIt = m_transactions.project(it); @@ -1355,20 +1911,26 @@ void WalletGreen::transactionDeleted(ITransfersSubscription* object, const Hash& } CryptoNote::ITransfersContainer* container = &object->getContainer(); + updateBalance(container); deleteUnlockTransactionJob(transactionHash); - m_change.erase(transactionHash); - deleteSpentOutputs(transactionHash); - m_transactions.get().modify(it, [] (CryptoNote::WalletTransaction& tx) { - tx.state = WalletTransactionState::CANCELLED; - tx.blockHeight = WALLET_UNCONFIRMED_TRANSACTION_HEIGHT; + bool updated = false; + m_transactions.get().modify(it, [&updated](CryptoNote::WalletTransaction& tx) { + if (tx.state == WalletTransactionState::CREATED || tx.state == WalletTransactionState::SUCCEEDED) { + tx.state = WalletTransactionState::CANCELLED; + updated = true; + } + + if (tx.blockHeight != WALLET_UNCONFIRMED_TRANSACTION_HEIGHT) { + tx.blockHeight = WALLET_UNCONFIRMED_TRANSACTION_HEIGHT; + updated = true; + } }); - auto rndIt = m_transactions.project(it); - auto id = std::distance(m_transactions.get().begin(), rndIt); - - updateBalance(container); - pushEvent(makeTransactionUpdatedEvent(id)); + if (updated) { + auto transactionId = getTransactionId(transactionHash); + pushEvent(makeTransactionUpdatedEvent(transactionId)); + } } void WalletGreen::insertUnlockTransactionJob(const Hash& transactionHash, uint32_t blockHeight, CryptoNote::ITransfersContainer* container) { @@ -1381,6 +1943,39 @@ void WalletGreen::deleteUnlockTransactionJob(const Hash& transactionHash) { index.erase(transactionHash); } +void WalletGreen::startBlockchainSynchronizer() { + if (!m_walletsContainer.empty() && !m_blockchainSynchronizerStarted) { + m_blockchainSynchronizer.start(); + m_blockchainSynchronizerStarted = true; + } +} + +void WalletGreen::stopBlockchainSynchronizer() { + if (m_blockchainSynchronizerStarted) { + m_blockchainSynchronizer.stop(); + m_blockchainSynchronizerStarted = false; + } +} + +void WalletGreen::addUnconfirmedTransaction(const ITransactionReader& transaction) { + System::RemoteContext context(m_dispatcher, [this, &transaction] { + return m_blockchainSynchronizer.addUnconfirmedTransaction(transaction).get(); + }); + + auto ec = context.get(); + if (ec) { + throw std::system_error(ec, "Failed to add unconfirmed transaction"); + } +} + +void WalletGreen::removeUnconfirmedTransaction(const Crypto::Hash& transactionHash) { + System::RemoteContext context(m_dispatcher, [this, &transactionHash] { + m_blockchainSynchronizer.removeUnconfirmedTransaction(transactionHash).get(); + }); + + context.get(); +} + void WalletGreen::updateBalance(CryptoNote::ITransfersContainer* container) { auto it = m_walletsContainer.get().find(container); @@ -1391,17 +1986,6 @@ void WalletGreen::updateBalance(CryptoNote::ITransfersContainer* container) { uint64_t actual = container->balance(ITransfersContainer::IncludeAllUnlocked); uint64_t pending = container->balance(ITransfersContainer::IncludeAllLocked); - uint64_t unconfirmedBalance = countSpentBalance(&(*it)); - - actual -= unconfirmedBalance; - - //xxx: i don't like this special case. Decompose this function - if (container == m_walletsContainer.get()[0].container) { - uint64_t change = 0; - std::for_each(m_change.begin(), m_change.end(), [&change] (const TransactionChanges::value_type& item) { change += item.second; }); - pending += change; - } - if (it->actualBalance < actual) { m_actualBalance += actual - it->actualBalance; } else { @@ -1423,7 +2007,7 @@ void WalletGreen::updateBalance(CryptoNote::ITransfersContainer* container) { const WalletRecord& WalletGreen::getWalletRecord(const PublicKey& key) const { auto it = m_walletsContainer.get().find(key); if (it == m_walletsContainer.get().end()) { - throw std::system_error(std::make_error_code(std::errc::invalid_argument)); + throw std::system_error(make_error_code(error::WALLET_NOT_FOUND)); } return *it; @@ -1437,7 +2021,7 @@ const WalletRecord& WalletGreen::getWalletRecord(const std::string& address) con const WalletRecord& WalletGreen::getWalletRecord(CryptoNote::ITransfersContainer* container) const { auto it = m_walletsContainer.get().find(container); if (it == m_walletsContainer.get().end()) { - throw std::system_error(std::make_error_code(std::errc::invalid_argument)); + throw std::system_error(make_error_code(error::WALLET_NOT_FOUND)); } return *it; @@ -1447,54 +2031,12 @@ CryptoNote::AccountPublicAddress WalletGreen::parseAddress(const std::string& ad CryptoNote::AccountPublicAddress pubAddr; if (!m_currency.parseAccountAddressString(address, pubAddr)) { - throw std::system_error(std::make_error_code(std::errc::invalid_argument)); + throw std::system_error(make_error_code(error::BAD_ADDRESS)); } return pubAddr; } -bool WalletGreen::isOutputUsed(const TransactionOutputInformation& out) const { - return m_spentOutputs.get().find(boost::make_tuple(out.transactionHash, out.outputInTransaction)) - != - m_spentOutputs.get().end(); -} - -void WalletGreen::markOutputsSpent(const Hash& transactionHash,const std::vector& selectedTransfers) { - auto& index = m_spentOutputs.get(); - - for (const auto& output: selectedTransfers) { - index.insert( {output.out.amount, output.out.transactionHash, output.out.outputInTransaction, output.wallet, transactionHash} ); - } -} - -void WalletGreen::deleteSpentOutputs(const Hash& transactionHash) { - auto& index = m_spentOutputs.get(); - index.erase(transactionHash); -} - -uint64_t WalletGreen::countSpentBalance(const WalletRecord* wallet) { - uint64_t amount = 0; - - auto bounds = m_spentOutputs.get().equal_range(wallet); - for (auto it = bounds.first; it != bounds.second; ++it) { - amount += it->amount; - } - - return amount; -} - -void WalletGreen::updateUsedWalletsBalances(const std::vector& selectedTransfers) { - std::set wallets; - - // wallet #0 recieves change, so we have to update it after transfer - wallets.insert(const_cast(&m_walletsContainer.get()[0])); - - std::for_each(selectedTransfers.begin(), selectedTransfers.end(), [&wallets] (const OutputToTransfer& output) { wallets.insert(output.wallet); } ); - std::for_each(wallets.begin(), wallets.end(), [this] (WalletRecord* wallet) { - this->updateBalance(wallet->container); - }); -} - void WalletGreen::throwIfStopped() const { if (m_stopped) { throw std::system_error(make_error_code(error::OPERATION_CANCELLED)); @@ -1517,6 +2059,10 @@ WalletGreen::WalletTrackingMode WalletGreen::getTrackingMode() const { } size_t WalletGreen::createFusionTransaction(uint64_t threshold, uint64_t mixin) { + Tools::ScopeExit releaseContext([this] { + m_dispatcher.yield(); + }); + System::EventLock lk(m_readyEvent); throwIfNotInitialized(); @@ -1575,6 +2121,7 @@ size_t WalletGreen::createFusionTransaction(uint64_t threshold, uint64_t mixin) fusionTransaction = makeTransaction(std::vector{decomposedOutputs}, keysInfo, "", 0); transactionSize = getTransactionSize(*fusionTransaction); + ++round; } while (transactionSize > m_currency.fusionTxMaxSize() && fusionInputs.size() >= m_currency.fusionTxMinInputCount()); @@ -1582,28 +2129,7 @@ size_t WalletGreen::createFusionTransaction(uint64_t threshold, uint64_t mixin) throw std::runtime_error("Unable to create fusion transaction"); } - size_t transactionId = insertOutgoingTransactionAndPushEvent(fusionTransaction->getTransactionHash(), 0, 0, fusionTransaction->getExtra(), 0); - m_fusionTxsCache.emplace(transactionId, true); - - std::string address = m_currency.accountAddressAsString({m_walletsContainer.get().begin()->spendPublicKey, m_viewPublicKey }); - WalletTransfer destination = {WalletTransferType::USUAL, address, 0}; - pushBackOutgoingTransfers(transactionId, std::vector {destination}); - - markOutputsSpent(fusionTransaction->getTransactionHash(), fusionInputs); - - try { - sendTransaction(fusionTransaction.get()); - } catch (std::exception&) { - updateTransactionStateAndPushEvent(transactionId, WalletTransactionState::FAILED); - deleteSpentOutputs(fusionTransaction->getTransactionHash()); - throw; - } - - m_change[fusionTransaction->getTransactionHash()] = transactionAmount; - updateTransactionStateAndPushEvent(transactionId, WalletTransactionState::SUCCEEDED); - updateUsedWalletsBalances(fusionInputs); - - return transactionId; + return validateSaveAndSendTransaction(*fusionTransaction, {}, true, true); } WalletGreen::ReceiverAmounts WalletGreen::decomposeFusionOutputs(uint64_t inputsAmount) { @@ -1666,8 +2192,7 @@ bool WalletGreen::isFusionTransaction(const WalletTransaction& walletTx) const { } if (!gotTx) { - int64_t ignore; - gotTx = wallet.container->getTransactionInformation(walletTx.hash, txInfo, ignore); + gotTx = wallet.container->getTransactionInformation(walletTx.hash, txInfo); } } @@ -1693,7 +2218,7 @@ IFusionManager::EstimateResult WalletGreen::estimate(uint64_t threshold) const { for (size_t walletIndex = 0; walletIndex < walletOuts.size(); ++walletIndex) { for (auto& out : walletOuts[walletIndex].outs) { uint8_t powerOfTen = 0; - if (m_currency.isAmountApplicableInFusionTransactionInput(out.amount, threshold, powerOfTen) && !isOutputUsed(out)) { + if (m_currency.isAmountApplicableInFusionTransactionInput(out.amount, threshold, powerOfTen)) { assert(powerOfTen < std::numeric_limits::digits10 + 1); bucketSizes[powerOfTen]++; } @@ -1719,7 +2244,7 @@ std::vector WalletGreen::pickRandomFusionInputs(u for (size_t walletIndex = 0; walletIndex < walletOuts.size(); ++walletIndex) { for (auto& out : walletOuts[walletIndex].outs) { uint8_t powerOfTen = 0; - if (m_currency.isAmountApplicableInFusionTransactionInput(out.amount, threshold, powerOfTen) && !isOutputUsed(out)) { + if (m_currency.isAmountApplicableInFusionTransactionInput(out.amount, threshold, powerOfTen)) { allFusionReadyOuts.push_back({std::move(out), walletOuts[walletIndex].wallet}); assert(powerOfTen < std::numeric_limits::digits10 + 1); bucketSizes[powerOfTen]++; @@ -1778,4 +2303,201 @@ std::vector WalletGreen::pickRandomFusionInputs(u return trimmedSelectedOuts; } +std::vector WalletGreen::getTransactionsInBlocks(uint32_t blockIndex, size_t count) const { + if (count == 0) { + throw std::system_error(make_error_code(error::WRONG_PARAMETERS), "blocks count must be greater than zero"); + } + + std::vector result; + + if (blockIndex >= m_blockchain.size()) { + return result; + } + + auto& blockHeightIndex = m_transactions.get(); + uint32_t stopIndex = static_cast(std::min(m_blockchain.size(), blockIndex + count)); + + for (uint32_t height = blockIndex; height < stopIndex; ++height) { + TransactionsInBlockInfo info; + info.blockHash = m_blockchain[height]; + + auto lowerBound = blockHeightIndex.lower_bound(height); + auto upperBound = blockHeightIndex.upper_bound(height); + for (auto it = lowerBound; it != upperBound; ++it) { + if (it->state != WalletTransactionState::SUCCEEDED) { + continue; + } + + WalletTransactionWithTransfers transaction; + transaction.transaction = *it; + + transaction.transfers = getTransactionTransfers(*it); + + info.transactions.emplace_back(std::move(transaction)); + } + + result.emplace_back(std::move(info)); + } + + return result; +} + +Crypto::Hash WalletGreen::getBlockHashByIndex(uint32_t blockIndex) const { + assert(blockIndex < m_blockchain.size()); + return m_blockchain.get()[blockIndex]; +} + +std::vector WalletGreen::getTransactionTransfers(const WalletTransaction& transaction) const { + auto& transactionIdIndex = m_transactions.get(); + + auto it = transactionIdIndex.iterator_to(transaction); + assert(it != transactionIdIndex.end()); + + size_t transactionId = std::distance(transactionIdIndex.begin(), it); + size_t transfersCount = getTransactionTransferCount(transactionId); + + std::vector result; + result.reserve(transfersCount); + + for (size_t transferId = 0; transferId < transfersCount; ++transferId) { + result.push_back(getTransactionTransfer(transactionId, transferId)); + } + + return result; +} + +void WalletGreen::filterOutTransactions(WalletTransactions& transactions, WalletTransfers& transfers, std::function&& pred) const { + size_t cancelledTransactions = 0; + + auto& index = m_transactions.get(); + for (size_t i = 0; i < m_transactions.size(); ++i) { + const WalletTransaction& transaction = index[i]; + + if (pred(transaction)) { + ++cancelledTransactions; + continue; + } + + transactions.push_back(transaction); + std::vector transactionTransfers = getTransactionTransfers(transaction); + for (auto& transfer: transactionTransfers) { + transfers.push_back(TransactionTransferPair {i - cancelledTransactions, std::move(transfer)} ); + } + } +} + +void WalletGreen::getViewKeyKnownBlocks(const Crypto::PublicKey& viewPublicKey) { + std::vector blockchain = m_synchronizer.getViewKeyKnownBlocks(m_viewPublicKey); + m_blockchain.insert(m_blockchain.end(), blockchain.begin(), blockchain.end()); +} + +///pre: changeDestinationAddress belongs to current container +///pre: source address belongs to current container +CryptoNote::AccountPublicAddress WalletGreen::getChangeDestination(const std::string& changeDestinationAddress, const std::vector& sourceAddresses) const { + if (!changeDestinationAddress.empty()) { + return parseAccountAddressString(changeDestinationAddress, m_currency); + } + + if (m_walletsContainer.size() == 1) { + return AccountPublicAddress { m_walletsContainer.get()[0].spendPublicKey, m_viewPublicKey }; + } + + assert(sourceAddresses.size() == 1 && isMyAddress(sourceAddresses[0])); + return parseAccountAddressString(sourceAddresses[0], m_currency); +} + +bool WalletGreen::isMyAddress(const std::string& addressString) const { + CryptoNote::AccountPublicAddress address = parseAccountAddressString(addressString, m_currency); + return m_viewPublicKey == address.viewPublicKey && m_walletsContainer.get().count(address.spendPublicKey) != 0; +} + +void WalletGreen::deleteContainerFromUnlockTransactionJobs(const ITransfersContainer* container) { + for (auto it = m_unlockTransactionsJob.begin(); it != m_unlockTransactionsJob.end();) { + if (it->container == container) { + it = m_unlockTransactionsJob.erase(it); + } else { + ++it; + } + } +} + +std::vector WalletGreen::deleteTransfersForAddress(const std::string& address, std::vector& deletedTransactions) { + assert(!address.empty()); + + int64_t deletedInputs = 0; + int64_t deletedOutputs = 0; + + int64_t unknownInputs = 0; + + bool transfersLeft = false; + size_t firstTransactionTransfer = 0; + + std::vector updatedTransactions; + + for (size_t i = 0; i < m_transfers.size(); ++i) { + WalletTransfer& transfer = m_transfers[i].second; + + if (transfer.address == address) { + if (transfer.amount >= 0) { + deletedOutputs += transfer.amount; + } else { + deletedInputs += transfer.amount; + transfer.address = ""; + } + } else if (transfer.address.empty()) { + if (transfer.amount < 0) { + unknownInputs += transfer.amount; + } + } else if (isMyAddress(transfer.address)) { + transfersLeft = true; + } + + size_t transactionId = m_transfers[i].first; + if ((i == m_transfers.size() - 1) || (transactionId != m_transfers[i + 1].first)) { + //the last transfer for current transaction + + size_t transfersBeforeMerge = m_transfers.size(); + if (deletedInputs != 0) { + adjustTransfer(transactionId, firstTransactionTransfer, "", deletedInputs + unknownInputs); + } + + assert(transfersBeforeMerge >= m_transfers.size()); + i -= transfersBeforeMerge - m_transfers.size(); + + auto& randomIndex = m_transactions.get(); + + randomIndex.modify(std::next(randomIndex.begin(), transactionId), [transfersLeft, deletedInputs, deletedOutputs] (WalletTransaction& transaction) { + transaction.totalAmount -= deletedInputs + deletedOutputs; + + if (!transfersLeft) { + transaction.state = WalletTransactionState::DELETED; + } + }); + + if (!transfersLeft) { + deletedTransactions.push_back(transactionId); + } + + if (deletedInputs != 0 || deletedOutputs != 0) { + updatedTransactions.push_back(transactionId); + } + + //reset values for next transaction + deletedInputs = 0; + deletedOutputs = 0; + unknownInputs = 0; + transfersLeft = false; + firstTransactionTransfer = i + 1; + } + } + + return updatedTransactions; +} + +void WalletGreen::deleteFromUncommitedTransactions(const std::vector& deletedTransactions) { + for (auto transactionId: deletedTransactions) { + m_uncommitedTransactions.erase(transactionId); + } +} + } //namespace CryptoNote diff --git a/src/Wallet/WalletGreen.h b/src/Wallet/WalletGreen.h index 5d3cbfd4..5424170f 100755 --- a/src/Wallet/WalletGreen.h +++ b/src/Wallet/WalletGreen.h @@ -20,6 +20,7 @@ #include "IWallet.h" #include +#include #include "IFusionManager.h" #include "WalletIndices.h" @@ -34,6 +35,7 @@ namespace CryptoNote { class WalletGreen : public IWallet, ITransfersObserver, IBlockchainSynchronizerObserver, + ITransfersSynchronizerObserver, IFusionManager { public: WalletGreen(System::Dispatcher& dispatcher, const Currency& currency, INode& node, uint32_t transactionSoftLockTime = 1); @@ -50,6 +52,7 @@ public: virtual size_t getAddressCount() const override; virtual std::string getAddress(size_t index) const override; virtual KeyPair getAddressSpendKey(size_t index) const override; + virtual KeyPair getAddressSpendKey(const std::string& address) const override; virtual KeyPair getViewKey() const override; virtual std::string createAddress() override; virtual std::string createAddress(const Crypto::SecretKey& spendSecretKey) override; @@ -66,12 +69,20 @@ public: virtual size_t getTransactionTransferCount(size_t transactionIndex) const override; virtual WalletTransfer getTransactionTransfer(size_t transactionIndex, size_t transferIndex) const override; - virtual size_t transfer(const WalletOrder& destination, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0) override; - virtual size_t transfer(const std::vector& destinations, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0) override; - virtual size_t transfer(const std::string& sourceAddress, const WalletOrder& destination, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0) override; - virtual size_t transfer(const std::string& sourceAddress, const std::vector& destinations, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0) override; + virtual WalletTransactionWithTransfers getTransaction(const Crypto::Hash& transactionHash) const override; + virtual std::vector getTransactions(const Crypto::Hash& blockHash, size_t count) const override; + virtual std::vector getTransactions(uint32_t blockIndex, size_t count) const override; + virtual std::vector getBlockHashes(uint32_t blockIndex, size_t count) const override; + virtual uint32_t getBlockCount() const override; + virtual std::vector getUnconfirmedTransactions() const override; + virtual std::vector getDelayedTransactionIds() const override; + virtual size_t transfer(const TransactionParameters& sendingTransaction) override; + virtual size_t makeTransaction(const TransactionParameters& sendingTransaction) override; + virtual void commitTransaction(size_t) override; + virtual void rollbackUncommitedTransaction(size_t) override; + virtual void start() override; virtual void stop() override; virtual WalletEvent getEvent() override; @@ -110,9 +121,26 @@ protected: std::vector outs; }; + typedef std::pair TransfersRange; + + struct AddressAmounts { + int64_t input = 0; + int64_t output = 0; + }; + + struct ContainerAmounts { + ITransfersContainer* container; + AddressAmounts amounts; + }; + + typedef std::unordered_map TransfersMap; + virtual void onError(ITransfersSubscription* object, uint32_t height, std::error_code ec) override; + virtual void onTransactionUpdated(ITransfersSubscription* object, const Crypto::Hash& transactionHash) override; - void transactionUpdated(ITransfersSubscription* object, const Crypto::Hash& transactionHash); + virtual void onTransactionUpdated(const Crypto::PublicKey& viewPublicKey, const Crypto::Hash& transactionHash, + const std::vector& containers) override; + void transactionUpdated(const TransactionInformation& transactionInfo, const std::vector& containerAmountsList); virtual void onTransactionDeleted(ITransfersSubscription* object, const Crypto::Hash& transactionHash) override; void transactionDeleted(ITransfersSubscription* object, const Crypto::Hash& transactionHash); @@ -123,8 +151,21 @@ protected: void onSynchronizationProgressUpdated(uint32_t processedBlockCount, uint32_t totalBlockCount); void onSynchronizationCompleted(); + virtual void onBlocksAdded(const Crypto::PublicKey& viewPublicKey, const std::vector& blockHashes) override; + void blocksAdded(const std::vector& blockHashes); + + virtual void onBlockchainDetach(const Crypto::PublicKey& viewPublicKey, uint32_t blockIndex) override; + void blocksRollback(uint32_t blockIndex); + + virtual void onTransactionDeleteBegin(const Crypto::PublicKey& viewPublicKey, Crypto::Hash transactionHash) override; + void transactionDeleteBegin(Crypto::Hash transactionHash); + + virtual void onTransactionDeleteEnd(const Crypto::PublicKey& viewPublicKey, Crypto::Hash transactionHash) override; + void transactionDeleteEnd(Crypto::Hash transactionHash); + std::vector pickWalletsWithMoney() const; WalletOuts pickWallet(const std::string& address); + std::vector pickWallets(const std::vector& addresses); void updateBalance(CryptoNote::ITransfersContainer* container); void unlockBalances(uint32_t height); @@ -134,24 +175,31 @@ protected: const WalletRecord& getWalletRecord(CryptoNote::ITransfersContainer* container) const; CryptoNote::AccountPublicAddress parseAddress(const std::string& address) const; - void addWallet(const Crypto::PublicKey& spendPublicKey, const Crypto::SecretKey& spendSecretKey, uint64_t creationTimestamp); - bool isOutputUsed(const TransactionOutputInformation& out) const; - void markOutputsSpent(const Crypto::Hash& transactionHash, const std::vector& selectedTransfers); - void deleteSpentOutputs(const Crypto::Hash& transactionHash); - uint64_t countSpentBalance(const WalletRecord* wallet); - void updateUsedWalletsBalances(const std::vector& selectedTransfers); + std::string addWallet(const Crypto::PublicKey& spendPublicKey, const Crypto::SecretKey& spendSecretKey, uint64_t creationTimestamp); AccountKeys makeAccountKeys(const WalletRecord& wallet) const; size_t getTransactionId(const Crypto::Hash& transactionHash) const; void pushEvent(const WalletEvent& event); bool isFusionTransaction(const WalletTransaction& walletTx) const; - size_t doTransfer(std::vector&& wallets, + struct PreparedTransaction { + std::unique_ptr transaction; + std::vector destinations; + uint64_t neededMoney; + uint64_t changeAmount; + }; + + void prepareTransaction(std::vector&& wallets, const std::vector& orders, uint64_t fee, uint64_t mixIn, const std::string& extra, uint64_t unlockTimestamp, - const DonationSettings& donation); + const DonationSettings& donation, + const CryptoNote::AccountPublicAddress& changeDestinationAddress, + PreparedTransaction& preparedTransaction); + + void validateTransactionParameters(const TransactionParameters& transactionParameters); + size_t doTransfer(const TransactionParameters& transactionParameters); void requestMixinOuts(const std::vector& selectedTransfers, uint64_t mixIn, @@ -175,23 +223,38 @@ protected: std::unique_ptr makeTransaction(const std::vector& decomposedOutputs, std::vector& keysInfo, const std::string& extra, uint64_t unlockTimestamp); - void sendTransaction(ITransaction* tx); + void sendTransaction(const CryptoNote::Transaction& cryptoNoteTransaction); + size_t validateSaveAndSendTransaction(const ITransactionReader& transaction, const std::vector& destinations, bool isFusion, bool send); - size_t insertBlockchainTransactionAndPushEvent(const TransactionInformation& info, int64_t txBalance); - size_t insertOutgoingTransactionAndPushEvent(const Crypto::Hash& transactionHash, - int64_t totalAmount, uint64_t fee, const BinaryArray& extra, uint64_t unlockTimestamp); + size_t insertBlockchainTransaction(const TransactionInformation& info, int64_t txBalance); + size_t insertOutgoingTransactionAndPushEvent(const Crypto::Hash& transactionHash, uint64_t fee, const BinaryArray& extra, uint64_t unlockTimestamp); void updateTransactionStateAndPushEvent(size_t transactionId, WalletTransactionState state); - void updateWalletTransactionInfoAndPushEvent(size_t transactionId, const CryptoNote::TransactionInformation& info, bool transfersUpdated); - void insertIncomingTransfer(size_t txId, const std::string& address, int64_t amount); - void pushBackOutgoingTransfers(size_t txId, const std::vector &destinations); + bool updateWalletTransactionInfo(size_t transactionId, const CryptoNote::TransactionInformation& info, int64_t totalAmount); + bool updateTransactionTransfers(size_t transactionId, const std::vector& containerAmountsList, + int64_t allInputsAmount, int64_t allOutputsAmount); + TransfersMap getKnownTransfersMap(size_t transactionId, size_t firstTransferIdx) const; + bool updateAddressTransfers(size_t transactionId, size_t firstTransferIdx, const std::string& address, int64_t knownAmount, int64_t targetAmount); + bool updateUnknownTransfers(size_t transactionId, size_t firstTransferIdx, const std::unordered_set& myAddresses, + int64_t knownAmount, int64_t myAmount, int64_t totalAmount, bool isOutput); + void appendTransfer(size_t transactionId, size_t firstTransferIdx, const std::string& address, int64_t amount); + bool adjustTransfer(size_t transactionId, size_t firstTransferIdx, const std::string& address, int64_t amount); + bool eraseTransfers(size_t transactionId, size_t firstTransferIdx, std::function&& predicate); + bool eraseTransfersByAddress(size_t transactionId, size_t firstTransferIdx, const std::string& address, bool eraseOutputTransfers); + bool eraseForeignTransfers(size_t transactionId, size_t firstTransferIdx, const std::unordered_set& knownAddresses, bool eraseOutputTransfers); + void pushBackOutgoingTransfers(size_t txId, const std::vector& destinations); void insertUnlockTransactionJob(const Crypto::Hash& transactionHash, uint32_t blockHeight, CryptoNote::ITransfersContainer* container); void deleteUnlockTransactionJob(const Crypto::Hash& transactionHash); + void startBlockchainSynchronizer(); + void stopBlockchainSynchronizer(); + void addUnconfirmedTransaction(const ITransactionReader& transaction); + void removeUnconfirmedTransaction(const Crypto::Hash& transactionHash); void unsafeLoad(std::istream& source, const std::string& password); void unsafeSave(std::ostream& destination, bool saveDetails, bool saveCache); std::vector pickRandomFusionInputs(uint64_t threshold, size_t minInputCount, size_t maxInputCount); ReceiverAmounts decomposeFusionOutputs(uint64_t inputsAmount); + enum class WalletState { INITIALIZED, NOT_INITIALIZED @@ -205,21 +268,33 @@ protected: WalletTrackingMode getTrackingMode() const; - std::pair getTransactionTransfers(size_t transactionIndex) const; + TransfersRange getTransactionTransfersRange(size_t transactionIndex) const; + std::vector getTransactionsInBlocks(uint32_t blockIndex, size_t count) const; + Crypto::Hash getBlockHashByIndex(uint32_t blockIndex) const; + + std::vector getTransactionTransfers(const WalletTransaction& transaction) const; + void filterOutTransactions(WalletTransactions& transactions, WalletTransfers& transfers, std::function&& pred) const; + void getViewKeyKnownBlocks(const Crypto::PublicKey& viewPublicKey); + CryptoNote::AccountPublicAddress getChangeDestination(const std::string& changeDestinationAddress, const std::vector& sourceAddresses) const; + bool isMyAddress(const std::string& address) const; + + void deleteContainerFromUnlockTransactionJobs(const ITransfersContainer* container); + std::vector deleteTransfersForAddress(const std::string& address, std::vector& deletedTransactions); + void deleteFromUncommitedTransactions(const std::vector& deletedTransactions); System::Dispatcher& m_dispatcher; const Currency& m_currency; INode& m_node; - bool m_stopped = false; + bool m_stopped; WalletsContainer m_walletsContainer; - SpentOutputs m_spentOutputs; UnlockTransactionJobs m_unlockTransactionsJob; WalletTransactions m_transactions; WalletTransfers m_transfers; //sorted - TransactionChanges m_change; mutable std::unordered_map m_fusionTxsCache; // txIndex -> isFusion + UncommitedTransactions m_uncommitedTransactions; + bool m_blockchainSynchronizerStarted; BlockchainSynchronizer m_blockchainSynchronizer; TransfersSyncronizer m_synchronizer; @@ -227,18 +302,20 @@ protected: std::queue m_events; mutable System::Event m_readyEvent; - WalletState m_state = WalletState::NOT_INITIALIZED; + WalletState m_state; std::string m_password; Crypto::PublicKey m_viewPublicKey; Crypto::SecretKey m_viewSecretKey; - uint64_t m_actualBalance = 0; - uint64_t m_pendingBalance = 0; + uint64_t m_actualBalance; + uint64_t m_pendingBalance; uint64_t m_upperTransactionSizeLimit; uint32_t m_transactionSoftLockTime; + + BlockHashesContainer m_blockchain; }; } //namespace CryptoNote diff --git a/src/Wallet/WalletIndices.h b/src/Wallet/WalletIndices.h index 82cd7ad0..f9f6f152 100644 --- a/src/Wallet/WalletIndices.h +++ b/src/Wallet/WalletIndices.h @@ -17,6 +17,7 @@ #pragma once +#include #include #include "ITransfersContainer.h" @@ -43,14 +44,6 @@ struct WalletRecord { time_t creationTimestamp; }; -struct SpentOutput { - uint64_t amount; - Crypto::Hash transactionHash; - uint32_t outputInTransaction; - const WalletRecord* wallet; - Crypto::Hash spendingTransactionHash; -}; - struct RandomAccessIndex {}; struct KeysIndex {}; struct TransfersContainerIndex {}; @@ -61,6 +54,7 @@ struct BlockHeightIndex {}; struct TransactionHashIndex {}; struct TransactionIndex {}; +struct BlockHashIndex {}; typedef boost::multi_index_container < WalletRecord, @@ -73,27 +67,6 @@ typedef boost::multi_index_container < > > WalletsContainer; -struct OutputIndex: boost::multi_index::composite_key < - SpentOutput, - BOOST_MULTI_INDEX_MEMBER(SpentOutput, Crypto::Hash, transactionHash), - BOOST_MULTI_INDEX_MEMBER(SpentOutput, uint32_t, outputInTransaction) -> {}; - -typedef boost::multi_index_container < - SpentOutput, - boost::multi_index::indexed_by < - boost::multi_index::hashed_unique < boost::multi_index::tag , - OutputIndex - >, - boost::multi_index::hashed_non_unique < boost::multi_index::tag , - BOOST_MULTI_INDEX_MEMBER(SpentOutput, Crypto::Hash, spendingTransactionHash) - >, - boost::multi_index::hashed_non_unique < boost::multi_index::tag , - BOOST_MULTI_INDEX_MEMBER(SpentOutput, const WalletRecord *, wallet) - > - > -> SpentOutputs; - struct UnlockTransactionJob { uint32_t blockHeight; CryptoNote::ITransfersContainer* container; @@ -106,7 +79,7 @@ typedef boost::multi_index_container < boost::multi_index::ordered_non_unique < boost::multi_index::tag , BOOST_MULTI_INDEX_MEMBER(UnlockTransactionJob, uint32_t, blockHeight) >, - boost::multi_index::hashed_unique < boost::multi_index::tag , + boost::multi_index::hashed_non_unique < boost::multi_index::tag , BOOST_MULTI_INDEX_MEMBER(UnlockTransactionJob, Crypto::Hash, transactionHash) > > @@ -118,13 +91,28 @@ typedef boost::multi_index_container < boost::multi_index::random_access < boost::multi_index::tag >, boost::multi_index::hashed_unique < boost::multi_index::tag , boost::multi_index::member + >, + boost::multi_index::ordered_non_unique < boost::multi_index::tag , + boost::multi_index::member > > > WalletTransactions; -typedef std::unordered_map TransactionChanges; - typedef std::pair TransactionTransferPair; typedef std::vector WalletTransfers; +typedef std::map UncommitedTransactions; + +typedef boost::multi_index_container< + Crypto::Hash, + boost::multi_index::indexed_by < + boost::multi_index::random_access< + boost::multi_index::tag + >, + boost::multi_index::hashed_unique< + boost::multi_index::tag, + boost::multi_index::identity + > + > +> BlockHashesContainer; } diff --git a/src/Wallet/WalletSerialization.cpp b/src/Wallet/WalletSerialization.cpp index 1b923d38..4087da41 100755 --- a/src/Wallet/WalletSerialization.cpp +++ b/src/Wallet/WalletSerialization.cpp @@ -25,6 +25,7 @@ #include "Common/StdInputStream.h" #include "Common/StdOutputStream.h" #include "CryptoNoteCore/CryptoNoteSerialization.h" +#include "CryptoNoteCore/CryptoNoteTools.h" #include "Serialization/BinaryOutputStreamSerializer.h" #include "Serialization/BinaryInputStreamSerializer.h" @@ -49,7 +50,7 @@ struct WalletRecordDto { }; //DO NOT CHANGE IT -struct SpentOutputDto { +struct ObsoleteSpentOutputDto { uint64_t amount; Hash transactionHash; uint32_t outputInTransaction; @@ -58,7 +59,7 @@ struct SpentOutputDto { }; //DO NOT CHANGE IT -struct ChangeDto { +struct ObsoleteChangeDto { Hash txHash; uint64_t amount; }; @@ -121,7 +122,7 @@ void serialize(WalletRecordDto& value, CryptoNote::ISerializer& serializer) { serializer(value.creationTimestamp, "creation_timestamp"); } -void serialize(SpentOutputDto& value, CryptoNote::ISerializer& serializer) { +void serialize(ObsoleteSpentOutputDto& value, CryptoNote::ISerializer& serializer) { serializer(value.amount, "amount"); serializer(value.transactionHash, "transaction_hash"); serializer(value.outputInTransaction, "output_in_transaction"); @@ -129,7 +130,7 @@ void serialize(SpentOutputDto& value, CryptoNote::ISerializer& serializer) { serializer(value.spendingTransactionHash, "spending_transaction_hash"); } -void serialize(ChangeDto& value, CryptoNote::ISerializer& serializer) { +void serialize(ObsoleteChangeDto& value, CryptoNote::ISerializer& serializer) { serializer(value.txHash, "transaction_hash"); serializer(value.amount, "amount"); } @@ -273,7 +274,7 @@ CryptoNote::WalletTransfer convert(const CryptoNote::WalletLegacyTransfer& tr) { namespace CryptoNote { -const uint32_t WalletSerializer::SERIALIZATION_VERSION = 3; +const uint32_t WalletSerializer::SERIALIZATION_VERSION = 5; void CryptoContext::incIv() { uint64_t * i = reinterpret_cast(&iv.data[0]); @@ -288,12 +289,11 @@ WalletSerializer::WalletSerializer( uint64_t& pendingBalance, WalletsContainer& walletsContainer, TransfersSyncronizer& synchronizer, - SpentOutputs& spentOutputs, UnlockTransactionJobs& unlockTransactions, - TransactionChanges& change, WalletTransactions& transactions, WalletTransfers& transfers, - uint32_t transactionSoftLockTime + uint32_t transactionSoftLockTime, + UncommitedTransactions& uncommitedTransactions ) : m_transfersObserver(transfersObserver), m_viewPublicKey(viewPublicKey), @@ -302,12 +302,11 @@ WalletSerializer::WalletSerializer( m_pendingBalance(pendingBalance), m_walletsContainer(walletsContainer), m_synchronizer(synchronizer), - m_spentOutputs(spentOutputs), m_unlockTransactions(unlockTransactions), - m_change(change), m_transactions(transactions), m_transfers(transfers), - m_transactionSoftLockTime(transactionSoftLockTime) + m_transactionSoftLockTime(transactionSoftLockTime), + uncommitedTransactions(uncommitedTransactions) { } void WalletSerializer::save(const std::string& password, Common::IOutputStream& destination, bool saveDetails, bool saveCache) { @@ -331,9 +330,8 @@ void WalletSerializer::save(const std::string& password, Common::IOutputStream& if (saveCache) { saveBalances(destination, saveCache, cryptoContext); saveTransfersSynchronizer(destination, cryptoContext); - saveSpentOutputs(destination, cryptoContext); saveUnlockTransactionsJobs(destination, cryptoContext); - saveChange(destination, cryptoContext); + saveUncommitedTransactions(destination, cryptoContext); } s.endObject(); @@ -426,29 +424,6 @@ void WalletSerializer::saveTransfersSynchronizer(Common::IOutputStream& destinat cryptoContext.incIv(); } -void WalletSerializer::saveSpentOutputs(Common::IOutputStream& destination, CryptoContext& cryptoContext) { - auto& index = m_spentOutputs.get(); - - uint64_t outsCount = index.size(); - serializeEncrypted(outsCount, "spent_outputs_count", cryptoContext, destination); - cryptoContext.incIv(); - - for (const auto& o: index) { - auto it = m_walletsContainer.get().iterator_to(*o.wallet); - uint64_t walletIndex = std::distance(m_walletsContainer.get().begin(), it); - - SpentOutputDto dto; - dto.amount = o.amount; - dto.transactionHash = o.transactionHash; - dto.outputInTransaction = o.outputInTransaction; - dto.walletIndex = walletIndex; - dto.spendingTransactionHash = o.spendingTransactionHash; - - serializeEncrypted(dto, "", cryptoContext, destination); - cryptoContext.incIv(); - } -} - void WalletSerializer::saveUnlockTransactionsJobs(Common::IOutputStream& destination, CryptoContext& cryptoContext) { auto& index = m_unlockTransactions.get(); auto& wallets = m_walletsContainer.get(); @@ -476,19 +451,8 @@ void WalletSerializer::saveUnlockTransactionsJobs(Common::IOutputStream& destina } } -void WalletSerializer::saveChange(Common::IOutputStream& destination, CryptoContext& cryptoContext) { - uint64_t count = m_change.size(); - serializeEncrypted(count, "changes_count", cryptoContext, destination); - cryptoContext.incIv(); - - for (const auto& kv: m_change) { - ChangeDto dto; - dto.txHash = kv.first; - dto.amount = kv.second; - - serializeEncrypted(dto, "", cryptoContext, destination); - cryptoContext.incIv(); - } +void WalletSerializer::saveUncommitedTransactions(Common::IOutputStream& destination, CryptoContext& cryptoContext) { + serializeEncrypted(uncommitedTransactions, "uncommited_transactions", cryptoContext, destination); } void WalletSerializer::saveTransactions(Common::IOutputStream& destination, CryptoContext& cryptoContext) { @@ -510,6 +474,7 @@ void WalletSerializer::saveTransfers(Common::IOutputStream& destination, CryptoC for (const auto& kv: m_transfers) { uint64_t txId = kv.first; + WalletTransferDto tr(kv.second, SERIALIZATION_VERSION); serializeEncrypted(txId, "transaction_id", cryptoContext, destination); @@ -559,12 +524,33 @@ void WalletSerializer::loadWallet(Common::IInputStream& source, const std::strin loadTransfers(source, cryptoContext, version); } + if (version < 5) { + updateTransfersSign(); + cache = false; + } + if (cache) { loadBalances(source, cryptoContext); loadTransfersSynchronizer(source, cryptoContext); - loadSpentOutputs(source, cryptoContext); + if (version < 5) { + loadObsoleteSpentOutputs(source, cryptoContext); + } + loadUnlockTransactionsJobs(source, cryptoContext); - loadChange(source, cryptoContext); + + if (version < 5) { + loadObsoleteChange(source, cryptoContext); + } + + if (version > 3) { + loadUncommitedTransactions(source, cryptoContext); + + if (version >= 5) { + initTransactionPool(); + } + } + } else { + resetCachedBalance(); } if (details && cache) { @@ -762,30 +748,15 @@ void WalletSerializer::loadTransfersSynchronizer(Common::IInputStream& source, C m_synchronizer.load(stream); } -void WalletSerializer::loadSpentOutputs(Common::IInputStream& source, CryptoContext& cryptoContext) { - auto& index = m_spentOutputs.get(); - auto& walletsIndex = m_walletsContainer.get(); - const uint64_t walletsSize = walletsIndex.size(); - +void WalletSerializer::loadObsoleteSpentOutputs(Common::IInputStream& source, CryptoContext& cryptoContext) { uint64_t count = 0; deserializeEncrypted(count, "spent_outputs_count", cryptoContext, source); cryptoContext.incIv(); for (uint64_t i = 0; i < count; ++i) { - SpentOutputDto dto; + ObsoleteSpentOutputDto dto; deserializeEncrypted(dto, "", cryptoContext, source); cryptoContext.incIv(); - - assert(dto.walletIndex < walletsSize); - - SpentOutput output; - output.amount = dto.amount; - output.transactionHash = dto.transactionHash; - output.outputInTransaction = dto.outputInTransaction; - output.spendingTransactionHash = dto.spendingTransactionHash; - output.wallet = &walletsIndex[dto.walletIndex]; - - index.insert(std::move(output)); } } @@ -814,17 +785,37 @@ void WalletSerializer::loadUnlockTransactionsJobs(Common::IInputStream& source, } } -void WalletSerializer::loadChange(Common::IInputStream& source, CryptoContext& cryptoContext) { +void WalletSerializer::loadObsoleteChange(Common::IInputStream& source, CryptoContext& cryptoContext) { uint64_t count = 0; deserializeEncrypted(count, "changes_count", cryptoContext, source); cryptoContext.incIv(); for (uint64_t i = 0; i < count; i++) { - ChangeDto dto; + ObsoleteChangeDto dto; deserializeEncrypted(dto, "", cryptoContext, source); cryptoContext.incIv(); + } +} - m_change[dto.txHash] = dto.amount; +void WalletSerializer::loadUncommitedTransactions(Common::IInputStream& source, CryptoContext& cryptoContext) { + deserializeEncrypted(uncommitedTransactions, "uncommited_transactions", cryptoContext, source); +} + +void WalletSerializer::initTransactionPool() { + std::unordered_set uncommitedTransactionsSet; + std::transform(uncommitedTransactions.begin(), uncommitedTransactions.end(), std::inserter(uncommitedTransactionsSet, uncommitedTransactionsSet.end()), + [](const UncommitedTransactions::value_type& pair) { + return getObjectHash(pair.second); + }); + m_synchronizer.initTransactionPool(uncommitedTransactionsSet); +} + +void WalletSerializer::resetCachedBalance() { + for (auto it = m_walletsContainer.begin(); it != m_walletsContainer.end(); ++it) { + m_walletsContainer.modify(it, [](WalletRecord& wallet) { + wallet.actualBalance = 0; + wallet.pendingBalance = 0; + }); } } @@ -838,9 +829,8 @@ void WalletSerializer::updateTransactionsBaseStatus() { auto& wallets = m_walletsContainer.get(); TransactionInformation txInfo; auto it = std::find_if(std::begin(wallets), std::end(wallets), [&](const WalletRecord& rec) { - int64_t id = 0; assert(rec.container != nullptr); - return rec.container->getTransactionInformation(tx.hash, txInfo, id); + return rec.container->getTransactionInformation(tx.hash, txInfo); }); tx.isBase = it != std::end(wallets) && txInfo.totalAmountIn == 0; @@ -848,6 +838,18 @@ void WalletSerializer::updateTransactionsBaseStatus() { } } +void WalletSerializer::updateTransfersSign() { + auto it = m_transfers.begin(); + while (it != m_transfers.end()) { + if (it->second.amount < 0) { + it->second.amount = -it->second.amount; + ++it; + } else { + it = m_transfers.erase(it); + } + } +} + void WalletSerializer::loadTransactions(Common::IInputStream& source, CryptoContext& cryptoContext) { uint64_t count = 0; deserializeEncrypted(count, "transactions_count", cryptoContext, source); diff --git a/src/Wallet/WalletSerialization.h b/src/Wallet/WalletSerialization.h index 655d977e..84c18ade 100755 --- a/src/Wallet/WalletSerialization.h +++ b/src/Wallet/WalletSerialization.h @@ -45,12 +45,11 @@ public: uint64_t& pendingBalance, WalletsContainer& walletsContainer, TransfersSyncronizer& synchronizer, - SpentOutputs& spentOutputs, UnlockTransactionJobs& unlockTransactions, - TransactionChanges& change, WalletTransactions& transactions, WalletTransfers& transfers, - uint32_t transactionSoftLockTime + uint32_t transactionSoftLockTime, + UncommitedTransactions& uncommitedTransactions ); void save(const std::string& password, Common::IOutputStream& destination, bool saveDetails, bool saveCache); @@ -73,9 +72,8 @@ private: void saveWallets(Common::IOutputStream& destination, bool saveCache, CryptoContext& cryptoContext); void saveBalances(Common::IOutputStream& destination, bool saveCache, CryptoContext& cryptoContext); void saveTransfersSynchronizer(Common::IOutputStream& destination, CryptoContext& cryptoContext); - void saveSpentOutputs(Common::IOutputStream& destination, CryptoContext& cryptoContext); void saveUnlockTransactionsJobs(Common::IOutputStream& destination, CryptoContext& cryptoContext); - void saveChange(Common::IOutputStream& destination, CryptoContext& cryptoContext); + void saveUncommitedTransactions(Common::IOutputStream& destination, CryptoContext& cryptoContext); void saveTransactions(Common::IOutputStream& destination, CryptoContext& cryptoContext); void saveTransfers(Common::IOutputStream& destination, CryptoContext& cryptoContext); @@ -91,16 +89,20 @@ private: void subscribeWallets(); void loadBalances(Common::IInputStream& source, CryptoContext& cryptoContext); void loadTransfersSynchronizer(Common::IInputStream& source, CryptoContext& cryptoContext); - void loadSpentOutputs(Common::IInputStream& source, CryptoContext& cryptoContext); + void loadObsoleteSpentOutputs(Common::IInputStream& source, CryptoContext& cryptoContext); void loadUnlockTransactionsJobs(Common::IInputStream& source, CryptoContext& cryptoContext); - void loadChange(Common::IInputStream& source, CryptoContext& cryptoContext); + void loadObsoleteChange(Common::IInputStream& source, CryptoContext& cryptoContext); + void loadUncommitedTransactions(Common::IInputStream& source, CryptoContext& cryptoContext); void loadTransactions(Common::IInputStream& source, CryptoContext& cryptoContext); void loadTransfers(Common::IInputStream& source, CryptoContext& cryptoContext, uint32_t version); void loadWalletV1Keys(CryptoNote::BinaryInputStreamSerializer& serializer); void loadWalletV1Details(CryptoNote::BinaryInputStreamSerializer& serializer); void addWalletV1Details(const std::vector& txs, const std::vector& trs); + void initTransactionPool(); + void resetCachedBalance(); void updateTransactionsBaseStatus(); + void updateTransfersSign(); ITransfersObserver& m_transfersObserver; Crypto::PublicKey& m_viewPublicKey; @@ -109,12 +111,11 @@ private: uint64_t& m_pendingBalance; WalletsContainer& m_walletsContainer; TransfersSyncronizer& m_synchronizer; - SpentOutputs& m_spentOutputs; UnlockTransactionJobs& m_unlockTransactions; - TransactionChanges& m_change; WalletTransactions& m_transactions; WalletTransfers& m_transfers; uint32_t m_transactionSoftLockTime; + UncommitedTransactions& uncommitedTransactions; }; } //namespace CryptoNote diff --git a/src/Wallet/WalletUtils.cpp b/src/Wallet/WalletUtils.cpp new file mode 100644 index 00000000..a1ec7aff --- /dev/null +++ b/src/Wallet/WalletUtils.cpp @@ -0,0 +1,29 @@ +// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "WalletUtils.h" + +#include "CryptoNote.h" + +namespace CryptoNote { + +bool validateAddress(const std::string& address, const CryptoNote::Currency& currency) { + CryptoNote::AccountPublicAddress ignore; + return currency.parseAccountAddressString(address, ignore); +} + +} diff --git a/src/Wallet/WalletUtils.h b/src/Wallet/WalletUtils.h new file mode 100644 index 00000000..21bdf04a --- /dev/null +++ b/src/Wallet/WalletUtils.h @@ -0,0 +1,28 @@ +// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include + +#include "CryptoNoteCore/Currency.h" + +namespace CryptoNote { + +bool validateAddress(const std::string& address, const CryptoNote::Currency& currency); + +} diff --git a/src/WalletLegacy/WalletLegacy.cpp b/src/WalletLegacy/WalletLegacy.cpp index 39033b5f..752f7025 100755 --- a/src/WalletLegacy/WalletLegacy.cpp +++ b/src/WalletLegacy/WalletLegacy.cpp @@ -536,10 +536,11 @@ void WalletLegacy::onTransactionUpdated(ITransfersSubscription* object, const Ha std::shared_ptr event; TransactionInformation txInfo; - int64_t txBalance; - if (m_transferDetails->getTransactionInformation(transactionHash, txInfo, txBalance)) { + uint64_t amountIn; + uint64_t amountOut; + if (m_transferDetails->getTransactionInformation(transactionHash, txInfo, &amountIn, &amountOut)) { std::unique_lock lock(m_cacheMutex); - event = m_transactionsCache.onTransactionUpdated(txInfo, txBalance); + event = m_transactionsCache.onTransactionUpdated(txInfo, static_cast(amountOut) - static_cast(amountIn)); } if (event.get()) { diff --git a/src/crypto/tree-hash.c b/src/crypto/tree-hash.c index 27cd4558..fd4fc5d2 100644 --- a/src/crypto/tree-hash.c +++ b/src/crypto/tree-hash.c @@ -32,23 +32,23 @@ void tree_hash(const char (*hashes)[HASH_SIZE], size_t count, char *root_hash) { size_t i, j; size_t cnt = count - 1; char (*ints)[HASH_SIZE]; - for (i = 1; i < sizeof(size_t); i <<= 1) { + for (i = 1; i < 8 * sizeof(size_t); i <<= 1) { cnt |= cnt >> i; } cnt &= ~(cnt >> 1); ints = alloca(cnt * HASH_SIZE); memcpy(ints, hashes, (2 * cnt - count) * HASH_SIZE); for (i = 2 * cnt - count, j = 2 * cnt - count; j < cnt; i += 2, ++j) { - cn_fast_hash(hashes[i], 64, ints[j]); + cn_fast_hash(hashes[i], 2 * HASH_SIZE, ints[j]); } assert(i == count); while (cnt > 2) { cnt >>= 1; for (i = 0, j = 0; j < cnt; i += 2, ++j) { - cn_fast_hash(ints[i], 64, ints[j]); + cn_fast_hash(ints[i], 2 * HASH_SIZE, ints[j]); } } - cn_fast_hash(ints[0], 64, root_hash); + cn_fast_hash(ints[0], 2 * HASH_SIZE, root_hash); } } diff --git a/src/version.h.in b/src/version.h.in index 0bc423bc..38ba5460 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -1,4 +1,4 @@ #define BUILD_COMMIT_ID "@VERSION@" -#define PROJECT_VERSION "1.0.8.2" -#define PROJECT_VERSION_BUILD_NO "625" +#define PROJECT_VERSION "1.0.9" +#define PROJECT_VERSION_BUILD_NO "661" #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO "(" BUILD_COMMIT_ID ")" diff --git a/tests/TransfersTests/Tests.cpp b/tests/TransfersTests/Tests.cpp index d754d167..f0af6f5c 100644 --- a/tests/TransfersTests/Tests.cpp +++ b/tests/TransfersTests/Tests.cpp @@ -99,7 +99,25 @@ public: return std::error_code(); } - void getKnownPoolTxIds(std::vector& ids) override { + const std::unordered_set& getKnownPoolTxIds() const override { + //stub + static std::unordered_set empty; + return empty; + } + + std::error_code addUnconfirmedTransaction(const ITransactionReader& /*transaction*/) override { + throw std::runtime_error("Not implemented"); + } + + void removeUnconfirmedTransaction(const Crypto::Hash& /*transactionHash*/) override { + throw std::runtime_error("Not implemented"); + } + + virtual void addObserver(IBlockchainConsumerObserver* observer) override { + //stub + } + + virtual void removeObserver(IBlockchainConsumerObserver* observer) override { //stub } diff --git a/tests/UnitTests/ICoreStub.h b/tests/UnitTests/ICoreStub.h index e9295f16..f5587eca 100644 --- a/tests/UnitTests/ICoreStub.h +++ b/tests/UnitTests/ICoreStub.h @@ -63,7 +63,6 @@ public: virtual bool handle_incoming_block_blob(const CryptoNote::BinaryArray& block_blob, CryptoNote::block_verification_context& bvc, bool control_miner, bool relay_block) override { return false; } virtual bool handle_get_objects(CryptoNote::NOTIFY_REQUEST_GET_OBJECTS::request& arg, CryptoNote::NOTIFY_RESPONSE_GET_OBJECTS::request& rsp) override { return false; } virtual void on_synchronized() override {} - virtual bool is_ready() override { return true; } virtual bool getOutByMSigGIndex(uint64_t amount, uint64_t gindex, CryptoNote::MultisignatureOutput& out) override { return true; } virtual size_t addChain(const std::vector& chain) override; diff --git a/tests/UnitTests/PaymentGateTests.cpp b/tests/UnitTests/PaymentGateTests.cpp index 6fff5a9f..b9fb4063 100644 --- a/tests/UnitTests/PaymentGateTests.cpp +++ b/tests/UnitTests/PaymentGateTests.cpp @@ -23,6 +23,7 @@ #include #include "PaymentGate/WalletService.h" +#include "PaymentGate/WalletFactory.h" // test helpers #include "INodeStubs.h" @@ -45,7 +46,8 @@ public: } std::unique_ptr createWalletService(const WalletConfiguration& cfg) { - std::unique_ptr service(new WalletService(currency, dispatcher, nodeStub, cfg, logger)); + wallet.reset(WalletFactory::createWallet(currency, nodeStub, dispatcher)); + std::unique_ptr service(new WalletService(currency, dispatcher, nodeStub, *wallet, cfg, logger)); service->init(); return service; } @@ -61,6 +63,8 @@ protected: TestBlockchainGenerator generator; INodeTrivialRefreshStub nodeStub; System::Dispatcher dispatcher; + + std::unique_ptr wallet; }; @@ -90,15 +94,9 @@ TEST_F(PaymentGateTest, addTransaction) { System::Timer(dispatcher).sleep(std::chrono::seconds(2)); - uint64_t txCount = 0; - - ASSERT_TRUE(!service->getTransactionsCount(txCount)); - ASSERT_EQ(3, txCount); - uint64_t pending = 0, actual = 0; - ASSERT_TRUE(!service->getPendingBalance(pending)); - ASSERT_TRUE(!service->getActualBalance(actual)); + service->getBalance(actual, pending); ASSERT_NE(0, pending); ASSERT_NE(0, actual); @@ -106,6 +104,7 @@ TEST_F(PaymentGateTest, addTransaction) { ASSERT_EQ(pending * 2, actual); } +/* TEST_F(PaymentGateTest, DISABLED_sendTransaction) { auto cfg = createWalletConfiguration(); @@ -142,16 +141,16 @@ TEST_F(PaymentGateTest, DISABLED_sendTransaction) { uint64_t txId = 0; { - SendTransactionRequest req; - SendTransactionResponse res; + SendTransaction::Request req; + SendTransaction::Response res; - req.destinations.push_back(TransferDestination{ TEST_AMOUNT, recvAddress }); + req.transfers.push_back(WalletRpcOrder{ TEST_AMOUNT, recvAddress }); req.fee = currency.minimumFee(); - req.mixin = 1; + req.anonymity = 1; req.unlockTime = 0; req.paymentId = paymentIdStr; - ASSERT_TRUE(!service->sendTransaction(req, res)); + ASSERT_TRUE(!service->sendTransaction(req, res.transactionHash)); txId = res.transactionId; } @@ -263,4 +262,4 @@ TEST_F(PaymentGateTest, DISABLED_sendTransaction) { ASSERT_EQ(1, recvPayment.size()); ASSERT_EQ(TEST_AMOUNT / 2, recvPayment[0].amount); } -} +} */ diff --git a/tests/UnitTests/TestBcS.cpp b/tests/UnitTests/TestBcS.cpp index 33ff2f0f..2a468c1a 100755 --- a/tests/UnitTests/TestBcS.cpp +++ b/tests/UnitTests/TestBcS.cpp @@ -20,6 +20,7 @@ #include "Transfers/BlockchainSynchronizer.h" #include "Transfers/TransfersConsumer.h" +#include "crypto/hash.h" #include "CryptoNoteCore/TransactionApi.h" #include "CryptoNoteCore/CryptoNoteFormatUtils.h" #include "CryptoNoteCore/CryptoNoteTools.h" @@ -122,11 +123,21 @@ public: m_blockchain.push_back(genesisBlockHash); } + void addPoolTransaction(const Crypto::Hash& hash) { + m_pool.emplace(hash); + } + virtual SynchronizationStart getSyncStart() override { SynchronizationStart start = { 0, 0 }; return start; } + virtual void addObserver(IBlockchainConsumerObserver* observer) override { + } + + virtual void removeObserver(IBlockchainConsumerObserver* observer) override { + } + virtual void onBlockchainDetach(uint32_t height) override { assert(height < m_blockchain.size()); m_blockchain.resize(height); @@ -145,28 +156,32 @@ public: return m_blockchain; } - virtual void getKnownPoolTxIds(std::vector& ids) override { - ids.assign(m_pool.begin(), m_pool.end()); + virtual const std::unordered_set& getKnownPoolTxIds() const override { + return m_pool; } virtual std::error_code onPoolUpdated(const std::vector>& addedTransactions, const std::vector& deletedTransactions) override { for (const auto& tx: addedTransactions) { - Hash hash = tx->getTransactionHash(); - m_pool.push_back(reinterpret_cast(hash)); + m_pool.emplace(tx->getTransactionHash()); } for (auto& hash : deletedTransactions) { - auto pos = std::find(m_pool.begin(), m_pool.end(), hash); - if (pos != m_pool.end()) { - m_pool.erase(pos); - } + m_pool.erase(hash); } return std::error_code(); } + std::error_code addUnconfirmedTransaction(const ITransactionReader& /*transaction*/) override { + throw std::runtime_error("Not implemented"); + } + + void removeUnconfirmedTransaction(const Crypto::Hash& /*transactionHash*/) override { + throw std::runtime_error("Not implemented"); + } + private: - std::vector m_pool; + std::unordered_set m_pool; std::vector m_blockchain; }; @@ -483,18 +498,12 @@ TEST_F(BcSTest, serializationCheck) { class FunctorialPoolConsumerStub : public ConsumerStub { public: - FunctorialPoolConsumerStub(const Hash& genesisBlockHash) : ConsumerStub(genesisBlockHash) {} - virtual void getKnownPoolTxIds(std::vector& ids) override { - getKnownPoolTxIdsFunctor(ids); - } - virtual std::error_code onPoolUpdated(const std::vector>& addedTransactions, const std::vector& deletedTransactions) override { return onPoolUpdatedFunctor(addedTransactions, deletedTransactions); } - std::function&)> getKnownPoolTxIdsFunctor; std::function>&, const std::vector&)> onPoolUpdatedFunctor; }; @@ -511,8 +520,6 @@ TEST_F(BcSTest, firstPoolSynchronizationCheck) { auto tx2hash = getObjectHash(tx2); auto tx3hash = getObjectHash(tx3); - std::vector consumer1Pool = { tx1hash, tx2hash }; - std::vector consumer2Pool = { tx2hash, tx3hash }; std::unordered_set firstExpectedPool = { tx1hash, tx2hash, tx3hash }; std::unordered_set secondExpectedPool = { tx2hash }; @@ -523,8 +530,11 @@ TEST_F(BcSTest, firstPoolSynchronizationCheck) { FunctorialPoolConsumerStub c1(m_currency.genesisBlockHash()); FunctorialPoolConsumerStub c2(m_currency.genesisBlockHash()); - c1.getKnownPoolTxIdsFunctor = [&](std::vector& ids) { ids.assign(consumer1Pool.begin(), consumer1Pool.end()); }; - c2.getKnownPoolTxIdsFunctor = [&](std::vector& ids) { ids.assign(consumer2Pool.begin(), consumer2Pool.end()); }; + c1.addPoolTransaction(tx1hash); + c1.addPoolTransaction(tx2hash); + + c2.addPoolTransaction(tx2hash); + c2.addPoolTransaction(tx3hash); std::vector c1ResponseDeletedPool; std::vector c2ResponseDeletedPool; @@ -606,6 +616,7 @@ TEST_F(BcSTest, firstPoolSynchronizationCheck) { TEST_F(BcSTest, firstPoolSynchronizationCheckNonActual) { addConsumers(2); + m_consumers.front()->addPoolTransaction(Crypto::rand()); int requestsCount = 0; @@ -635,12 +646,12 @@ TEST_F(BcSTest, firstPoolSynchronizationCheckNonActual) { m_sync.removeObserver(&o1); o1.syncFunc = [](std::error_code) {}; - EXPECT_EQ(4, requestsCount); } TEST_F(BcSTest, firstPoolSynchronizationCheckGetPoolErr) { addConsumers(2); + m_consumers.front()->addPoolTransaction(Crypto::rand()); int requestsCount = 0; @@ -895,9 +906,6 @@ TEST_F(BcSTest, poolSynchronizationCheckConsumersNotififcation) { FunctorialPoolConsumerStub c1(m_currency.genesisBlockHash()); FunctorialPoolConsumerStub c2(m_currency.genesisBlockHash()); - c1.getKnownPoolTxIdsFunctor = [&](std::vector& ids) {}; - c2.getKnownPoolTxIdsFunctor = [&](std::vector& ids) {}; - bool c1Notified = false; bool c2Notified = false; c1.onPoolUpdatedFunctor = [&](const std::vector>& new_txs, const std::vector& deleted)->std::error_code { @@ -933,9 +941,6 @@ TEST_F(BcSTest, poolSynchronizationCheckConsumerReturnError) { FunctorialPoolConsumerStub c1(m_currency.genesisBlockHash()); FunctorialPoolConsumerStub c2(m_currency.genesisBlockHash()); - c1.getKnownPoolTxIdsFunctor = [&](std::vector& ids) {}; - c2.getKnownPoolTxIdsFunctor = [&](std::vector& ids) {}; - bool c1Notified = false; bool c2Notified = false; c1.onPoolUpdatedFunctor = [&](const std::vector>& new_txs, const std::vector& deleted)->std::error_code { diff --git a/tests/UnitTests/TestBlockchainGenerator.cpp b/tests/UnitTests/TestBlockchainGenerator.cpp index 523d030c..0051960e 100644 --- a/tests/UnitTests/TestBlockchainGenerator.cpp +++ b/tests/UnitTests/TestBlockchainGenerator.cpp @@ -210,6 +210,10 @@ void TestBlockchainGenerator::addToBlockchain(const CryptoNote::Transaction& tx) } void TestBlockchainGenerator::addToBlockchain(const std::vector& txs) { + addToBlockchain(txs, miner_acc); +} + +void TestBlockchainGenerator::addToBlockchain(const std::vector& txs, const CryptoNote::AccountBase& minerAddress) { std::list txsToBlock; for (const auto& tx: txs) { @@ -222,7 +226,7 @@ void TestBlockchainGenerator::addToBlockchain(const std::vector(tx.outputs[entry.indexOut].target); return true; } + +bool TestBlockchainGenerator::generateFromBaseTx(const CryptoNote::AccountBase& address) { + std::unique_lock lock(m_mutex); + addToBlockchain({}, address); + return true; +} \ No newline at end of file diff --git a/tests/UnitTests/TestBlockchainGenerator.h b/tests/UnitTests/TestBlockchainGenerator.h index 935aa21a..9b2ffe54 100644 --- a/tests/UnitTests/TestBlockchainGenerator.h +++ b/tests/UnitTests/TestBlockchainGenerator.h @@ -43,6 +43,7 @@ public: void addTxToBlockchain(const CryptoNote::Transaction& transaction); bool getTransactionByHash(const Crypto::Hash& hash, CryptoNote::Transaction& tx, bool checkTxPool = false); const CryptoNote::AccountBase& getMinerAccount() const; + bool generateFromBaseTx(const CryptoNote::AccountBase& address); void putTxToPool(const CryptoNote::Transaction& tx); void getPoolSymmetricDifference(std::vector&& known_pool_tx_ids, Crypto::Hash known_block_id, bool& is_bc_actual, @@ -96,6 +97,7 @@ private: void addToBlockchain(const CryptoNote::Transaction& tx); void addToBlockchain(const std::vector& txs); + void addToBlockchain(const std::vector& txs, const CryptoNote::AccountBase& minerAddress); void addTx(const CryptoNote::Transaction& tx); bool doGenerateTransactionsInOneBlock(CryptoNote::AccountPublicAddress const &address, size_t n); diff --git a/tests/UnitTests/TestTransfersConsumer.cpp b/tests/UnitTests/TestTransfersConsumer.cpp index 042b7470..adf0969b 100755 --- a/tests/UnitTests/TestTransfersConsumer.cpp +++ b/tests/UnitTests/TestTransfersConsumer.cpp @@ -742,8 +742,7 @@ TEST_F(TransfersConsumerTest, onNewBlocks_checkTransactionInformation) { ASSERT_TRUE(m_consumer.onNewBlocks(&blocks[0], 0, 2)); TransactionInformation info; - int64_t balance; - ASSERT_TRUE(container.getTransactionInformation(tx->getTransactionHash(), info, balance)); + ASSERT_TRUE(container.getTransactionInformation(tx->getTransactionHash(), info)); ASSERT_EQ(tx->getTransactionHash(), info.transactionHash); ASSERT_EQ(tx->getTransactionPublicKey(), info.publicKey); @@ -875,7 +874,7 @@ TEST_F(TransfersConsumerTest, onPoolUpdated_addTransactionDoesNotGetsGlobalIndic ASSERT_TRUE(m_node.calls_getTransactionOutsGlobalIndices.empty()); } -TEST_F(TransfersConsumerTest, onPoolUpdated_deleteTransaction) { +TEST_F(TransfersConsumerTest, onPoolUpdated_deleteTransactionNotDeleted) { auto& sub = addSubscription(); TransfersObserver observer; sub.addObserver(&observer); @@ -887,15 +886,42 @@ TEST_F(TransfersConsumerTest, onPoolUpdated_deleteTransaction) { m_consumer.onPoolUpdated({}, deleted); + ASSERT_EQ(0, observer.deleted.size()); +} + +TEST_F(TransfersConsumerTest, onPoolUpdated_deleteTransaction) { + const uint8_t TX_COUNT = 2; + auto& sub = addSubscription(); + TransfersObserver observer; + sub.addObserver(&observer); + + std::vector> added; + std::vector deleted; + + for (uint8_t i = 0; i < TX_COUNT; ++i) { + // construct tx + TestTransactionBuilder b1; + auto unknownSender = generateAccountKeys(); + b1.addTestInput(10000, unknownSender); + auto out = b1.addTestKeyOutput(10000, UNCONFIRMED_TRANSACTION_GLOBAL_OUTPUT_INDEX, m_accountKeys); + + auto tx = std::shared_ptr(b1.build().release()); + + std::unique_ptr prefix = createTransactionPrefix(convertTx(*tx)); + added.push_back(std::move(prefix)); + deleted.push_back(added.back()->getTransactionHash()); + } + + m_consumer.onPoolUpdated(added, {}); + m_consumer.onPoolUpdated({}, deleted); + ASSERT_EQ(deleted.size(), observer.deleted.size()); - ASSERT_EQ(reinterpret_cast(deleted[0]), observer.deleted[0]); - ASSERT_EQ(reinterpret_cast(deleted[1]), observer.deleted[1]); + ASSERT_EQ(deleted, observer.deleted); } TEST_F(TransfersConsumerTest, getKnownPoolTxIds_empty) { addSubscription(); - std::vector ids; - m_consumer.getKnownPoolTxIds(ids); + const std::unordered_set& ids = m_consumer.getKnownPoolTxIds(); ASSERT_TRUE(ids.empty()); } @@ -926,14 +952,13 @@ TEST_F(TransfersConsumerTest, getKnownPoolTxIds_returnsUnconfirmed) { v.push_back(createTransactionPrefix(convertTx(*txs[2]))); m_consumer.onPoolUpdated(v, {}); - std::vector ids; - m_consumer.getKnownPoolTxIds(ids); + const std::unordered_set& ids = m_consumer.getKnownPoolTxIds(); ASSERT_EQ(3, ids.size()); for (int i = 0; i < 3; ++i) { auto txhash = txs[i]->getTransactionHash(); - ASSERT_TRUE(std::find(ids.begin(), ids.end(), reinterpret_cast(txhash)) != ids.end()); + ASSERT_EQ(1, ids.count(txhash)); } } diff --git a/tests/UnitTests/TestTransfersContainer.cpp b/tests/UnitTests/TestTransfersContainer.cpp index 7ebdbc81..33c6f6a5 100755 --- a/tests/UnitTests/TestTransfersContainer.cpp +++ b/tests/UnitTests/TestTransfersContainer.cpp @@ -295,10 +295,12 @@ TEST_F(TransfersContainer_addTransaction, handlesAddingUnconfirmedOutputToKey) { ASSERT_TRUE(transfers.empty()); TransactionInformation txInfo; - int64_t txBalance; - ASSERT_TRUE(container.getTransactionInformation(tx->getTransactionHash(), txInfo, txBalance)); + uint64_t amountIn; + uint64_t amountOut; + ASSERT_TRUE(container.getTransactionInformation(tx->getTransactionHash(), txInfo, &amountIn, &amountOut)); ASSERT_EQ(blockInfo.height, txInfo.blockHeight); - ASSERT_EQ(TEST_OUTPUT_AMOUNT, txBalance); + ASSERT_EQ(0, amountIn); + ASSERT_EQ(TEST_OUTPUT_AMOUNT, amountOut); std::vector unconfirmedTransactions; container.getUnconfirmedTransactions(unconfirmedTransactions); @@ -339,10 +341,12 @@ TEST_F(TransfersContainer_addTransaction, handlesAddingConfirmedOutputToKey) { ASSERT_EQ(1, transfers.size()); TransactionInformation txInfo; - int64_t txBalance; - ASSERT_TRUE(container.getTransactionInformation(tx->getTransactionHash(), txInfo, txBalance)); + uint64_t amountIn; + uint64_t amountOut; + ASSERT_TRUE(container.getTransactionInformation(tx->getTransactionHash(), txInfo, &amountIn, &amountOut)); ASSERT_EQ(blockInfo.height, txInfo.blockHeight); - ASSERT_EQ(TEST_OUTPUT_AMOUNT, txBalance); + ASSERT_EQ(0, amountIn); + ASSERT_EQ(TEST_OUTPUT_AMOUNT, amountOut); std::vector unconfirmedTransactions; container.getUnconfirmedTransactions(unconfirmedTransactions); @@ -379,8 +383,7 @@ TEST_F(TransfersContainer_addTransaction, addingEmptyTransactionOuptutsDoesNotCh ASSERT_TRUE(transfers.empty()); TransactionInformation txInfo; - int64_t txBalance; - ASSERT_FALSE(container.getTransactionInformation(tx->getTransactionHash(), txInfo, txBalance)); + ASSERT_FALSE(container.getTransactionInformation(tx->getTransactionHash(), txInfo)); std::vector unconfirmedTransactions; container.getUnconfirmedTransactions(unconfirmedTransactions); diff --git a/tests/UnitTests/TestWallet.cpp b/tests/UnitTests/TestWallet.cpp index fedc5b81..28d63879 100755 --- a/tests/UnitTests/TestWallet.cpp +++ b/tests/UnitTests/TestWallet.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "Common/StringTools.h" #include "CryptoNoteCore/Currency.h" @@ -29,6 +30,7 @@ #include "TestBlockchainGenerator.h" #include "TransactionApiHelpers.h" #include +#include "Wallet/WalletErrors.h" #include "Wallet/WalletGreen.h" #include "WalletLegacy/WalletUserTransactionsCache.h" #include "WalletLegacy/WalletLegacySerializer.h" @@ -57,6 +59,9 @@ namespace CryptoNote { case WalletTransactionState::CREATED: o << "CREATED"; break; + case WalletTransactionState::DELETED: + o << "DELETED"; + break; } return o; } @@ -141,6 +146,10 @@ namespace CryptoNote { bool operator==(const IFusionManager::EstimateResult& lhs, const IFusionManager::EstimateResult& rhs) { return lhs.fusionReadyCount == rhs.fusionReadyCount && lhs.totalOutputCount == rhs.totalOutputCount; } + + bool operator<(const WalletTransfer& lhs, const WalletTransfer& rhs) { + return std::make_tuple(lhs.amount, lhs.address) < std::make_tuple(rhs.amount, rhs.address); + } } class WalletApi: public ::testing::Test { @@ -190,14 +199,19 @@ protected: void waitForActualBalance(uint64_t expected); void waitForActualBalance(CryptoNote::WalletGreen& wallet, uint64_t expected); - size_t sendMoneyToRandomAddressFrom(const std::string& address, uint64_t amount, uint64_t fee); - size_t sendMoneyToRandomAddressFrom(const std::string& address); + size_t sendMoneyToRandomAddressFrom(const std::string& address, uint64_t amount, uint64_t fee, const std::string& changeDestination); + size_t sendMoneyToRandomAddressFrom(const std::string& address, const std::string& changeDestination); size_t sendMoney(CryptoNote::WalletGreen& wallet, const std::string& to, uint64_t amount, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0); size_t sendMoney(const std::string& to, uint64_t amount, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0); size_t sendMoneyWithDonation(const std::string& to, uint64_t amount, uint64_t fee, const std::string& donationAddress, uint64_t donationAmount, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0); + size_t makeTransaction(const std::vector& sourceAdresses, const std::string& to, uint64_t amount, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0); + size_t makeTransaction(CryptoNote::WalletGreen& wallet, const std::vector& sourceAdresses, const std::string& to, uint64_t amount, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0); + size_t makeTransaction(const std::vector& sourceAdresses, const std::vector& orders, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0); + size_t makeTransaction(CryptoNote::WalletGreen& wallet, const std::vector& sourceAdresses, const std::vector& orders, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0); + void fillWalletWithDetailsCache(); void wait(uint64_t milliseconds); @@ -410,16 +424,21 @@ void WalletApi::generateAddressesWithPendingMoney(size_t count) { } } -size_t WalletApi::sendMoneyToRandomAddressFrom(const std::string& address, uint64_t amount, uint64_t fee) { +size_t WalletApi::sendMoneyToRandomAddressFrom(const std::string& address, uint64_t amount, uint64_t fee, const std::string& changeDestination) { CryptoNote::WalletOrder order; order.address = RANDOM_ADDRESS; order.amount = amount; - return alice.transfer(address, order, fee, 0); + CryptoNote::TransactionParameters params; + params.sourceAddresses = {address}; + params.destinations = {order}; + params.fee = fee; + params.changeDestination = changeDestination; + return alice.transfer(params); } -size_t WalletApi::sendMoneyToRandomAddressFrom(const std::string& address) { - return sendMoneyToRandomAddressFrom(address, SENT, FEE); +size_t WalletApi::sendMoneyToRandomAddressFrom(const std::string& address, const std::string& changeDestination) { + return sendMoneyToRandomAddressFrom(address, SENT, FEE, changeDestination); } void WalletApi::fillWalletWithDetailsCache() { @@ -428,14 +447,14 @@ void WalletApi::fillWalletWithDetailsCache() { auto alicePrev = alice.getActualBalance(); for (size_t i = 1; i < 5; ++i) { - sendMoneyToRandomAddressFrom(alice.getAddress(i)); + sendMoneyToRandomAddressFrom(alice.getAddress(i), alice.getAddress(0)); } node.updateObservers(); waitActualBalanceUpdated(alicePrev); for (size_t i = 5; i < 10; ++i) { - sendMoneyToRandomAddressFrom(alice.getAddress(i)); + sendMoneyToRandomAddressFrom(alice.getAddress(i), alice.getAddress(0)); } } @@ -444,7 +463,15 @@ size_t WalletApi::sendMoney(CryptoNote::WalletGreen& wallet, const std::string& order.address = to; order.amount = amount; - return wallet.transfer(order, fee, mixIn, extra, unlockTimestamp); + CryptoNote::TransactionParameters params; + params.destinations = {order}; + params.fee = fee; + params.mixIn = mixIn; + params.extra = extra; + params.unlockTimestamp = unlockTimestamp; + params.changeDestination = wallet.getAddress(0); + + return wallet.transfer(params); } size_t WalletApi::sendMoney(const std::string& to, uint64_t amount, uint64_t fee, uint64_t mixIn, const std::string& extra, uint64_t unlockTimestamp) { @@ -466,11 +493,110 @@ size_t WalletApi::sendMoneyWithDonation(const std::string& to, uint64_t amount, return alice.transfer(params); } +size_t WalletApi::makeTransaction( + const std::vector& sourceAdresses, + const std::string& to, + uint64_t amount, + uint64_t fee, + uint64_t mixIn, + const std::string& extra, + uint64_t unlockTimestamp) { + + return makeTransaction(alice, sourceAdresses, to, amount, fee, mixIn, extra, unlockTimestamp); +} + +size_t WalletApi::makeTransaction( + CryptoNote::WalletGreen& wallet, + const std::vector& sourceAdresses, + const std::string& to, + uint64_t amount, + uint64_t fee, + uint64_t mixIn, + const std::string& extra, + uint64_t unlockTimestamp) { + + CryptoNote::TransactionParameters params; + params.destinations = { {to, amount} }; + params.sourceAddresses = sourceAdresses; + params.fee = fee; + params.mixIn = mixIn; + params.extra = extra; + params.unlockTimestamp = unlockTimestamp; + + return wallet.makeTransaction(params); +} + +size_t WalletApi::makeTransaction( + const std::vector& sourceAdresses, + const std::vector& orders, + uint64_t fee, + uint64_t mixIn, + const std::string& extra, + uint64_t unlockTimestamp) { + + return makeTransaction(alice, sourceAdresses, orders, fee, mixIn, extra, unlockTimestamp); +} + +size_t WalletApi::makeTransaction( + CryptoNote::WalletGreen& wallet, + const std::vector& sourceAdresses, + const std::vector& orders, + uint64_t fee, + uint64_t mixIn, + const std::string& extra, + uint64_t unlockTimestamp) { + + CryptoNote::TransactionParameters params; + params.destinations = orders; + params.sourceAddresses = sourceAdresses; + params.fee = fee; + params.mixIn = mixIn; + params.extra = extra; + params.unlockTimestamp = unlockTimestamp; + + return wallet.makeTransaction(params); +} + void WalletApi::wait(uint64_t milliseconds) { System::Timer timer(dispatcher); timer.sleep(std::chrono::nanoseconds(milliseconds * 1000000)); } +auto transfersAmountSortingFunction = [] (const CryptoNote::WalletTransfer& lhs, const CryptoNote::WalletTransfer& rhs) { + return lhs.amount < rhs.amount; +}; + +std::vector getTransfersFromTransaction(CryptoNote::WalletGreen& wallet, size_t transactionId, bool isPositiveAmount) { + std::vector transfers; + size_t transfersCount = wallet.getTransactionTransferCount(transactionId); + + for (size_t i = 0; i < transfersCount; ++i) { + WalletTransfer transfer = wallet.getTransactionTransfer(transactionId, i); + + if (isPositiveAmount == (transfer.amount >= 0)) { + transfers.push_back(std::move(transfer)); + } + } + + return transfers; +} + +void sortTransfersByAmount(std::vector& transfers) { + std::sort(transfers.begin(), transfers.end(), transfersAmountSortingFunction); //sort by amount +} + +//returns sorted transfers by amount +std::vector getTransfersFromTransaction(CryptoNote::WalletGreen& wallet, size_t transactionId) { + auto result = getTransfersFromTransaction(wallet, transactionId, true); + auto neg = getTransfersFromTransaction(wallet, transactionId, false); + + result.insert(result.end(), neg.begin(), neg.end()); + + sortTransfersByAmount(result); + + return result; +} + static const uint64_t TEST_BLOCK_REWARD = 70368744177663; TEST_F(WalletApi, emptyBalance) { @@ -642,19 +768,24 @@ TEST_F(WalletApi, transferTooBigTransaction) { n.updateObservers(); waitActualBalanceUpdated(wallet, prev); - std::vector destinations; + CryptoNote::TransactionParameters params; for (size_t i = 0; i < bigTxOutputCount; ++i) { - destinations.push_back({ RANDOM_ADDRESS, 1 }); + params.destinations.push_back({ RANDOM_ADDRESS, 1 }); } - ASSERT_ANY_THROW(wallet.transfer(destinations, FEE)); + params.fee = FEE; + + ASSERT_ANY_THROW(wallet.transfer(params)); } TEST_F(WalletApi, balanceAfterTransfer) { generateAndUnlockMoney(); + auto prev = alice.getActualBalance(); sendMoney(RANDOM_ADDRESS, SENT, FEE); + waitActualBalanceUpdated(alice, prev); + ASSERT_EQ(TEST_BLOCK_REWARD - SENT - FEE, alice.getActualBalance() + alice.getPendingBalance()); } @@ -688,7 +819,8 @@ TEST_F(WalletApi, transferFromSpecificAddress) { auto prevActual = alice.getActualBalance(); auto prevPending = alice.getPendingBalance(); - sendMoneyToRandomAddressFrom(secondAddress); + //send change to aliceAddress + sendMoneyToRandomAddressFrom(secondAddress, aliceAddress); node.updateObservers(); waitActualBalanceUpdated(prevActual); @@ -696,8 +828,6 @@ TEST_F(WalletApi, transferFromSpecificAddress) { ASSERT_EQ(TEST_BLOCK_REWARD, alice.getActualBalance(aliceAddress)); - //NOTE: do not expect the rule 'actual + pending == previous - sent - fee' to work, - //because change is sent to address #0. ASSERT_NE(TEST_BLOCK_REWARD, alice.getActualBalance(secondAddress)); ASSERT_NE(0, alice.getPendingBalance(aliceAddress)); ASSERT_EQ(2 * TEST_BLOCK_REWARD - SENT - FEE, alice.getActualBalance() + alice.getPendingBalance()); @@ -1109,7 +1239,7 @@ TEST_F(WalletApi, uninitializedObject) { ASSERT_ANY_THROW(bob.getTransaction(0)); ASSERT_ANY_THROW(bob.getTransactionTransferCount(0)); ASSERT_ANY_THROW(bob.getTransactionTransfer(0, 0)); - ASSERT_ANY_THROW(sendMoneyToRandomAddressFrom(aliceAddress)); + ASSERT_ANY_THROW(sendMoneyToRandomAddressFrom(aliceAddress, aliceAddress)); ASSERT_ANY_THROW(bob.shutdown()); wait(100); } @@ -1118,8 +1248,12 @@ const size_t TX_PUB_KEY_EXTRA_SIZE = 33; TEST_F(WalletApi, checkSentTransaction) { generateAndUnlockMoney(); + + auto prev = alice.getActualBalance(); size_t txId = sendMoney(RANDOM_ADDRESS, SENT, FEE); + waitActualBalanceUpdated(alice, prev); + CryptoNote::WalletTransaction tx = alice.getTransaction(txId); ASSERT_EQ(CryptoNote::WalletTransactionState::SUCCEEDED, tx.state); ASSERT_EQ(0, tx.timestamp); @@ -1152,8 +1286,12 @@ TEST_F(WalletApi, checkSentTransactionWithExtra) { const std::string extra = createExtraNonce("\x01\x23\x45\x67\x89\xab\xcd\xef"); generateAndUnlockMoney(); + + auto prev = alice.getActualBalance(); size_t txId = sendMoney(RANDOM_ADDRESS, SENT, FEE, 0, extra); + waitActualBalanceUpdated(alice, prev); + CryptoNote::WalletTransaction tx = alice.getTransaction(txId); ASSERT_EQ(CryptoNote::WalletTransactionState::SUCCEEDED, tx.state); ASSERT_EQ(0, tx.timestamp); @@ -1274,7 +1412,7 @@ TEST_F(WalletApi, deleteAddresses) { EXPECT_EQ(0, alice.getPendingBalance()); } -TEST_F(WalletApi, incomingTxTransfer) { +TEST_F(WalletApi, incomingTxTransferWithChange) { generateAndUnlockMoney(); CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME); @@ -1287,8 +1425,8 @@ TEST_F(WalletApi, incomingTxTransfer) { node.updateObservers(); waitForTransactionCount(bob, 2); - EXPECT_EQ(1, bob.getTransactionTransferCount(0)); - ASSERT_EQ(1, bob.getTransactionTransferCount(1)); + EXPECT_EQ(3, bob.getTransactionTransferCount(0)); //sent from alice + received on bob + alice change + ASSERT_EQ(3, bob.getTransactionTransferCount(1)); auto tr1 = bob.getTransactionTransfer(0, 0); EXPECT_EQ(tr1.address, bob.getAddress(0)); @@ -1302,6 +1440,24 @@ TEST_F(WalletApi, incomingTxTransfer) { wait(100); } +TEST_F(WalletApi, incomingTxTransferWithoutChange) { + generator.getSingleOutputTransaction(parseAddress(aliceAddress), SENT + FEE); + unlockMoney(); + + CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME); + bob.initialize("pass2"); + bob.createAddress(); + + sendMoney(bob.getAddress(0), SENT, FEE); + + node.updateObservers(); + waitForTransactionCount(bob, 1); + + ASSERT_EQ(2, bob.getTransactionTransferCount(0)); + bob.shutdown(); + wait(100); +} + TEST_F(WalletApi, walletSendsTransactionUpdatedEventAfterAddingTransfer) { generateAndUnlockMoney(); @@ -1311,16 +1467,15 @@ TEST_F(WalletApi, walletSendsTransactionUpdatedEventAfterAddingTransfer) { bob.createAddress(); bob.createAddress(); - std::vector orders; - orders.emplace_back(CryptoNote::WalletOrder{ bob.getAddress(0), SENT }); - orders.emplace_back(CryptoNote::WalletOrder{ bob.getAddress(1), SENT }); - orders.emplace_back(CryptoNote::WalletOrder{ bob.getAddress(2), SENT }); - alice.transfer(orders, FEE, 0, "", 0); + CryptoNote::TransactionParameters params; + params.destinations.emplace_back(CryptoNote::WalletOrder{ bob.getAddress(0), SENT }); + params.destinations.emplace_back(CryptoNote::WalletOrder{ bob.getAddress(1), SENT }); + params.destinations.emplace_back(CryptoNote::WalletOrder{ bob.getAddress(2), SENT }); + params.fee = FEE; + alice.transfer(params); node.updateObservers(); ASSERT_TRUE(waitForWalletEvent(bob, CryptoNote::WalletEventType::TRANSACTION_CREATED, std::chrono::seconds(5))); - ASSERT_TRUE(waitForWalletEvent(bob, CryptoNote::WalletEventType::TRANSACTION_UPDATED, std::chrono::seconds(5))); - ASSERT_TRUE(waitForWalletEvent(bob, CryptoNote::WalletEventType::TRANSACTION_UPDATED, std::chrono::seconds(5))); bob.shutdown(); wait(100); @@ -1334,15 +1489,18 @@ TEST_F(WalletApi, walletCreatesTransferForEachTransactionFunding) { bob.createAddress(); bob.createAddress(); - std::vector orders; - orders.emplace_back(CryptoNote::WalletOrder{ bob.getAddress(0), SENT }); - orders.emplace_back(CryptoNote::WalletOrder{ bob.getAddress(1), 2 * SENT }); - alice.transfer(orders, FEE, 0, "", 0); + CryptoNote::TransactionParameters params; + params.destinations.emplace_back(CryptoNote::WalletOrder{ bob.getAddress(0), SENT }); + params.destinations.emplace_back(CryptoNote::WalletOrder{ bob.getAddress(1), 2 * SENT }); + + params.fee = FEE; + alice.transfer(params); node.updateObservers(); - ASSERT_TRUE(waitForWalletEvent(bob, CryptoNote::WalletEventType::TRANSACTION_UPDATED, std::chrono::seconds(5))); + ASSERT_TRUE(waitForWalletEvent(bob, CryptoNote::WalletEventType::TRANSACTION_CREATED, std::chrono::seconds(5))); - ASSERT_EQ(2, bob.getTransactionTransferCount(0)); + //2 incoming transfers to bob's addresses(0, 1) and one outgoing for alice(0) + change to alice(0) + ASSERT_EQ(4, bob.getTransactionTransferCount(0)); auto tr1 = bob.getTransactionTransfer(0, 0); auto tr2 = bob.getTransactionTransfer(0, 1); ASSERT_TRUE(tr1.address == bob.getAddress(0) || tr1.address == bob.getAddress(1)); @@ -1374,19 +1532,44 @@ TEST_F(WalletApi, hybridTxTransfer) { CryptoNote::WalletOrder tr1 { alice.getAddress(1), SENT }; CryptoNote::WalletOrder tr2 { alice.getAddress(2), 2 * SENT }; - alice.transfer({tr1, tr2}, FEE); + CryptoNote::TransactionParameters params; + params.destinations = {tr1, tr2}; + params.fee = FEE; + params.changeDestination = alice.getAddress(0); + alice.transfer(params); node.updateObservers(); dispatcher.yield(); - ASSERT_EQ(2, getTransactionUsualTransferCount(alice, 1)); + //2 incoming transfers to alice's addresses(1, 2) and one outgoing for alice(0) + ASSERT_EQ(3, getTransactionUsualTransferCount(alice, 1)); - EXPECT_EQ(tr1.address, alice.getTransactionTransfer(1, 0).address); - EXPECT_EQ(-static_cast(tr1.amount), alice.getTransactionTransfer(1, 0).amount); - EXPECT_EQ(WalletTransferType::USUAL, alice.getTransactionTransfer(1, 0).type); + WalletTransactionWithTransfers transfersWithTx; + ASSERT_NO_THROW({ + transfersWithTx = alice.getTransaction(alice.getTransaction(1).hash); + }); + //2 incoming transfers to alice's addresses(1, 2) and one outgoing for alice(0) + change to alice(0) + ASSERT_EQ(4, transfersWithTx.transfers.size()); - EXPECT_EQ(tr2.address, alice.getTransactionTransfer(1, 1).address); - EXPECT_EQ(-static_cast(tr2.amount), alice.getTransactionTransfer(1, 1).amount); - EXPECT_EQ(WalletTransferType::USUAL, alice.getTransactionTransfer(1, 1).type); + auto iter = std::find_if(transfersWithTx.transfers.begin(), transfersWithTx.transfers.end(), [&tr1](const WalletTransfer& transfer) { + return tr1.address == transfer.address && tr1.amount == transfer.amount && WalletTransferType::USUAL == transfer.type; + }); + EXPECT_NE(transfersWithTx.transfers.end(), iter); + + iter = std::find_if(transfersWithTx.transfers.begin(), transfersWithTx.transfers.end(), [&tr2](const WalletTransfer& transfer) { + return tr2.address == transfer.address && tr2.amount == transfer.amount && WalletTransferType::USUAL == transfer.type; + }); + EXPECT_NE(transfersWithTx.transfers.end(), iter); + + iter = std::find_if(transfersWithTx.transfers.begin(), transfersWithTx.transfers.end(), [this](const WalletTransfer& transfer) { + return alice.getAddress(0) == transfer.address && WalletTransferType::CHANGE == transfer.type; + }); + EXPECT_NE(transfersWithTx.transfers.end(), iter); + WalletTransfer changeTransfer = *iter; + + iter = std::find_if(transfersWithTx.transfers.begin(), transfersWithTx.transfers.end(), [this, &tr1, &tr2, &changeTransfer](const WalletTransfer& transfer) { + return alice.getAddress(0) == transfer.address && -static_cast(tr1.amount + tr2.amount + FEE + changeTransfer.amount) == transfer.amount && WalletTransferType::USUAL == transfer.type; + }); + EXPECT_NE(transfersWithTx.transfers.end(), iter); } TEST_F(WalletApi, doubleSpendJustSentOut) { @@ -1479,10 +1662,7 @@ TEST_F(WalletApi, DISABLED_loadTest) { steady_clock::time_point transferStart = steady_clock::now(); for (size_t i = 0; i < TRANSACTIONS_COUNT; ++i) { - CryptoNote::WalletOrder tr; - tr.amount = SENT; - tr.address = RANDOM_ADDRESS; - wallet.transfer(tr, FEE); + sendMoney(wallet, RANDOM_ADDRESS, SENT, FEE); } steady_clock::time_point transferEnd = steady_clock::now(); std::cout << "transfers took: " << duration_cast(transferEnd - transferStart).count() << " ms" << std::endl; @@ -1494,7 +1674,7 @@ TEST_F(WalletApi, DISABLED_loadTest) { TEST_F(WalletApi, transferSmallFeeTransactionThrows) { generateAndUnlockMoney(); - ASSERT_ANY_THROW(sendMoneyToRandomAddressFrom(alice.getAddress(0), SENT, currency.minimumFee() - 1)); + ASSERT_ANY_THROW(sendMoneyToRandomAddressFrom(alice.getAddress(0), SENT, currency.minimumFee() - 1, alice.getAddress(0))); } TEST_F(WalletApi, initializeWithKeysSucceded) { @@ -1929,17 +2109,18 @@ TEST_F(WalletApi, fusionManagerIsFusionTransactionSpent) { wallet.createAddress(); generateFusionOutputsAndUnlock(alice, node, currency, FUSION_THRESHOLD); + auto initialBalance = alice.getActualBalance(); auto id = alice.createFusionTransaction(FUSION_THRESHOLD, 0); ASSERT_NE(WALLET_INVALID_TRANSACTION_ID, id); unlockMoney(); - CryptoNote::WalletOrder order; - order.address = wallet.getAddress(0); - order.amount = alice.getActualBalance() - currency.minimumFee(); - alice.transfer(aliceAddress, order, currency.minimumFee(), 0); + waitForActualBalance(initialBalance); auto pending = wallet.getPendingBalance(); + ASSERT_NE(0, alice.getActualBalance()); + sendMoney(wallet.getAddress(0), alice.getActualBalance() - currency.minimumFee(), currency.minimumFee()); + node.updateObservers(); waitPendingBalanceUpdated(wallet, pending); @@ -1971,7 +2152,7 @@ TEST_F(WalletApi, donationTransferPresents) { auto donationTransfer = alice.getTransactionTransfer(transactionId, donationTransferId); ASSERT_EQ(WalletTransferType::DONATION, donationTransfer.type); - ASSERT_EQ(-static_cast(DONATION_THRESHOLD), donationTransfer.amount); + ASSERT_EQ(DONATION_THRESHOLD, donationTransfer.amount); ASSERT_EQ(RANDOM_ADDRESS, donationTransfer.address); } @@ -2066,3 +2247,1315 @@ TEST_F(WalletApi, transferThrowsIfDonationThresholdTooBig) { ASSERT_ANY_THROW(sendMoneyWithDonation(RANDOM_ADDRESS, SENT, FEE, RANDOM_ADDRESS, DONATION_THRESHOLD)); } + +namespace { + +class WalletApi_makeTransaction : public WalletApi { +public: + WalletApi_makeTransaction() : + WalletApi() { + } + +protected: + int makeAliceTransactionAndReturnErrorCode(const std::string& sourceAddress, const std::vector& destinations, + uint64_t fee, uint64_t mixIn, const std::string& extra = "") { + + try { + makeTransaction({sourceAddress}, destinations, fee, mixIn, extra); + } catch (std::system_error& e) { + return e.code().value(); + } + + return 0; + } + + std::string getExtraForBigTransaction() const { + size_t extraSize = 2 * currency.blockGrantedFullRewardZone(); + return std::string(extraSize, static_cast(0)); + } +}; + +} + +TEST_F(WalletApi_makeTransaction, throwsIfStopped) { + alice.stop(); + ASSERT_ANY_THROW(makeTransaction({}, RANDOM_ADDRESS, SENT, FEE, 0)); +} + +TEST_F(WalletApi_makeTransaction, throwsIfSourceAddressIsInvalid) { + generateAndUnlockMoney(); + ASSERT_ANY_THROW(makeTransaction({"not an address"}, RANDOM_ADDRESS, SENT, FEE, 0)); +} + +TEST_F(WalletApi_makeTransaction, throwsIfDestinationsIsEmpty) { + generateAndUnlockMoney(); + int error = makeAliceTransactionAndReturnErrorCode(alice.getAddress(0), {}, FEE, 0); + ASSERT_EQ(static_cast(error::WalletErrorCodes::ZERO_DESTINATION), error); +} + +TEST_F(WalletApi_makeTransaction, throwsIfDestinationsHasInvalidAddress) { + generateAndUnlockMoney(); + int error = makeAliceTransactionAndReturnErrorCode({alice.getAddress(0)}, { CryptoNote::WalletOrder{ "not an address", SENT } }, FEE, 0); + ASSERT_EQ(static_cast(error::WalletErrorCodes::BAD_ADDRESS), error); +} + +TEST_F(WalletApi_makeTransaction, throwsIfDestinationHasZeroAmount) { + generateAndUnlockMoney(); + int error = makeAliceTransactionAndReturnErrorCode({alice.getAddress(0)}, { CryptoNote::WalletOrder{ RANDOM_ADDRESS, 0 } }, FEE, 0); + ASSERT_EQ(static_cast(error::WalletErrorCodes::ZERO_DESTINATION), error); +} + +TEST_F(WalletApi_makeTransaction, throwsIfDestinationHasTooBigAmount) { + generateAndUnlockMoney(); + CryptoNote::WalletOrder order; + order.address = RANDOM_ADDRESS; + order.amount = static_cast(std::numeric_limits::max()) + 1; + int error = makeAliceTransactionAndReturnErrorCode({alice.getAddress(0)}, { order }, FEE, 0); + ASSERT_EQ(static_cast(CryptoNote::error::WalletErrorCodes::WRONG_AMOUNT), error); +} + +TEST_F(WalletApi_makeTransaction, throwsIfSumOfDestinationsAmountsOverflows) { + generateAndUnlockMoney(); + std::vector destinations; + destinations.push_back({ RANDOM_ADDRESS, SENT }); + destinations.push_back({ RANDOM_ADDRESS, std::numeric_limits::max() }); + int error = makeAliceTransactionAndReturnErrorCode({alice.getAddress(0)}, destinations, FEE, 0); + ASSERT_EQ(static_cast(error::WalletErrorCodes::WRONG_AMOUNT), error); +} + +TEST_F(WalletApi_makeTransaction, throwsIfFeeIsLessThanMinimumFee) { + if (currency.minimumFee() > 0) { + generateAndUnlockMoney(); + int error = makeAliceTransactionAndReturnErrorCode({alice.getAddress(0)}, { CryptoNote::WalletOrder{ RANDOM_ADDRESS, SENT } }, currency.minimumFee() - 1, 0); + ASSERT_EQ(static_cast(error::WalletErrorCodes::FEE_TOO_SMALL), error); + } +} + +TEST_F(WalletApi_makeTransaction, throwsIfWalletHasNotEnoughMoney) { + generateAndUnlockMoney(); + uint64_t available = alice.getActualBalance(); + ASSERT_GT(available, FEE); + uint64_t amount = available - FEE + 1; + int error = makeAliceTransactionAndReturnErrorCode({alice.getAddress(0)}, { CryptoNote::WalletOrder{ RANDOM_ADDRESS, amount } }, FEE, 0); + ASSERT_EQ(static_cast(error::WalletErrorCodes::WRONG_AMOUNT), error); +} + +TEST_F(WalletApi_makeTransaction, throwsIfMixInIsTooBig) { + generateAndUnlockMoney(); + uint64_t mixin = 10; + node.setMaxMixinCount(mixin - 1); + int error = makeAliceTransactionAndReturnErrorCode({alice.getAddress(0)}, { CryptoNote::WalletOrder{ RANDOM_ADDRESS, SENT } }, FEE, mixin); + ASSERT_EQ(static_cast(error::WalletErrorCodes::MIXIN_COUNT_TOO_BIG), error); +} + +TEST_F(WalletApi_makeTransaction, throwsIfTransactionIsTooBig) { + generateAndUnlockMoney(); + std::string extra = getExtraForBigTransaction(); + int error = makeAliceTransactionAndReturnErrorCode({alice.getAddress(0)}, { CryptoNote::WalletOrder{ RANDOM_ADDRESS, SENT } }, FEE, 0, extra); + ASSERT_EQ(static_cast(error::WalletErrorCodes::TRANSACTION_SIZE_TOO_BIG), error); +} + +TEST_F(WalletApi_makeTransaction, createdTransactionCanBeReceivedByGetTransactionAndHasCorrectFieldValues) { + const uint64_t MONEY = SENT + FEE + 1; + generator.getSingleOutputTransaction(parseAddress(aliceAddress), MONEY); + unlockMoney(); + + std::string extra = "some extra"; + uint64_t unlockTimestamp = 7823673; + + auto txId = makeTransaction({alice.getAddress(0)}, { CryptoNote::WalletOrder{ RANDOM_ADDRESS, SENT } }, FEE, 0, extra, unlockTimestamp); + + waitForTransactionUpdated(alice, txId); + + CryptoNote::WalletTransaction tx; + ASSERT_NO_THROW(tx = alice.getTransaction(txId)); + ASSERT_EQ(WalletTransactionState::CREATED, tx.state); + ASSERT_EQ(0, tx.timestamp); + ASSERT_EQ(WALLET_UNCONFIRMED_TRANSACTION_HEIGHT, tx.blockHeight); + ASSERT_EQ(-static_cast(SENT + FEE), tx.totalAmount); + ASSERT_EQ(FEE, tx.fee); + ASSERT_NE(0, tx.creationTime); + ASSERT_EQ(unlockTimestamp, tx.unlockTime); + ASSERT_NE(std::string::npos, tx.extra.find(extra)); + ASSERT_FALSE(tx.isBase); + + auto transfers = getTransfersFromTransaction(alice, txId); + ASSERT_EQ(3, transfers.size()); //one transfer for source address, one transfer for destination, one transfer for change + + //source + EXPECT_EQ(aliceAddress, transfers[0].address); + EXPECT_EQ(-static_cast(MONEY), transfers[0].amount); + + //change + EXPECT_EQ(aliceAddress, transfers[1].address); + EXPECT_EQ(MONEY - SENT - FEE, transfers[1].amount); + + //destination + EXPECT_EQ(RANDOM_ADDRESS, transfers[2].address); + EXPECT_EQ(SENT, transfers[2].amount); +} + +TEST_F(WalletApi_makeTransaction, methodLocksMoneyUsedInTransaction) { + generateAndUnlockMoney(); + + std::string sourceAddress = alice.getAddress(0); + uint64_t actualBefore = alice.getActualBalance(sourceAddress); + uint64_t pendingBefore = alice.getPendingBalance(sourceAddress); + auto txId = makeTransaction({sourceAddress}, { CryptoNote::WalletOrder{ RANDOM_ADDRESS, SENT } }, FEE, 0); + + waitForTransactionUpdated(alice, txId); + + ASSERT_GE(actualBefore - SENT - FEE, alice.getActualBalance(sourceAddress)); + ASSERT_LE(pendingBefore, alice.getPendingBalance(sourceAddress)); +} + +TEST_F(WalletApi_makeTransaction, ifFailedMoneyDoesNotLocked) { + generateAndUnlockMoney(); + + std::string sourceAddress = alice.getAddress(0); + uint64_t actualBefore = alice.getActualBalance(sourceAddress); + uint64_t pendingBefore = alice.getPendingBalance(sourceAddress); + ASSERT_ANY_THROW(makeTransaction({sourceAddress}, { CryptoNote::WalletOrder{ RANDOM_ADDRESS, SENT } }, FEE, 0, getExtraForBigTransaction())); + + ASSERT_EQ(actualBefore, alice.getActualBalance(sourceAddress)); + ASSERT_EQ(pendingBefore, alice.getPendingBalance(sourceAddress)); +} + +TEST_F(WalletApi_makeTransaction, sendsTransactionCreatedEvent) { + generateAndUnlockMoney(); + makeTransaction({alice.getAddress(0)}, { CryptoNote::WalletOrder{ RANDOM_ADDRESS, SENT } }, FEE, 0); + ASSERT_TRUE(waitForWalletEvent(alice, WalletEventType::TRANSACTION_CREATED, std::chrono::seconds(5))); +} + +TEST_F(WalletApi_makeTransaction, ifFailedDoesNotSendTransactionCreatedEvent) { + generateAndUnlockMoney(); + + System::Context eventContext(dispatcher, [this]() { + bool res; + + for (;;) { + try { + CryptoNote::WalletEvent event = alice.getEvent(); + if (event.type == WalletEventType::TRANSACTION_CREATED) { + res = true; + break; + } + } catch (System::InterruptedException&) { + res = false; + break; + } + } + + return res; + }); + + ASSERT_ANY_THROW(makeTransaction({alice.getAddress(0)}, { CryptoNote::WalletOrder{ RANDOM_ADDRESS, SENT } }, FEE, 0, getExtraForBigTransaction())); + + dispatcher.yield(); + eventContext.interrupt(); + ASSERT_FALSE(eventContext.get()); +} + +namespace { + +class WalletApi_commitTransaction : public WalletApi { +public: + WalletApi_commitTransaction() : + WalletApi() { + } + +protected: + size_t generateMoneyAndMakeAliceTransaction() { + generateAndUnlockMoney(); + return makeTransaction({alice.getAddress(0)}, { CryptoNote::WalletOrder{ RANDOM_ADDRESS, SENT } }, FEE, 0); + } + + int commitAliceTransactionAndReturnErrorCode(size_t transactionId) { + try { + alice.commitTransaction(transactionId); + } catch (std::system_error& e) { + return e.code().value(); + } + + return 0; + } +}; + +} + +TEST_F(WalletApi_commitTransaction, throwsIfStopped) { + auto txId = generateMoneyAndMakeAliceTransaction(); + alice.stop(); + ASSERT_ANY_THROW(alice.commitTransaction(txId)); +} + +TEST_F(WalletApi_commitTransaction, throwsIfTransactionIdIsInvalid) { + auto txId = generateMoneyAndMakeAliceTransaction(); + int error = commitAliceTransactionAndReturnErrorCode(txId + 1); + ASSERT_EQ(static_cast(error::WalletErrorCodes::INDEX_OUT_OF_RANGE), error); +} + +TEST_F(WalletApi_commitTransaction, throwsIfTransactionIsInSucceededState) { + auto txId = generateMoneyAndMakeAliceTransaction(); + alice.commitTransaction(txId); + + int error = commitAliceTransactionAndReturnErrorCode(txId); + ASSERT_EQ(static_cast(error::WalletErrorCodes::TX_TRANSFER_IMPOSSIBLE), error); +} + +TEST_F(WalletApi_commitTransaction, canSendTransactionAfterFail) { + auto txId = generateMoneyAndMakeAliceTransaction(); + node.setNextTransactionError(); + ASSERT_ANY_THROW(alice.commitTransaction(txId)); + + ASSERT_NO_THROW(alice.commitTransaction(txId)); +} + +TEST_F(WalletApi_commitTransaction, throwsIfTransactionIsInCancelledState) { + auto txId = generateMoneyAndMakeAliceTransaction(); + alice.rollbackUncommitedTransaction(txId); + + int error = commitAliceTransactionAndReturnErrorCode(txId); + ASSERT_EQ(static_cast(error::WalletErrorCodes::TX_TRANSFER_IMPOSSIBLE), error); +} + +TEST_F(WalletApi_commitTransaction, changesTransactionStateToSucceededIfTransactionSent) { + auto txId = generateMoneyAndMakeAliceTransaction(); + alice.commitTransaction(txId); + auto tx = alice.getTransaction(txId); + ASSERT_EQ(WalletTransactionState::SUCCEEDED, tx.state); +} + +TEST_F(WalletApi_commitTransaction, remainsTransactionStateCreatedIfTransactionSendFailed) { + auto txId = generateMoneyAndMakeAliceTransaction(); + node.setNextTransactionError(); + ASSERT_ANY_THROW(alice.commitTransaction(txId)); + auto tx = alice.getTransaction(txId); + ASSERT_EQ(WalletTransactionState::CREATED, tx.state); +} + +TEST_F(WalletApi_commitTransaction, doesNotUnlockMoneyIfTransactionCommitFailed) { + generateAndUnlockMoney(); + + std::string sourceAddress = alice.getAddress(0); + auto txId = makeTransaction({alice.getAddress(0)}, { CryptoNote::WalletOrder{ RANDOM_ADDRESS, SENT } }, FEE, 0); + + uint64_t actualBefore = alice.getActualBalance(sourceAddress); + uint64_t pendingBefore = alice.getPendingBalance(sourceAddress); + + node.setNextTransactionError(); + ASSERT_ANY_THROW(alice.commitTransaction(txId)); + + ASSERT_EQ(actualBefore, alice.getActualBalance(sourceAddress)); + ASSERT_EQ(pendingBefore, alice.getPendingBalance(sourceAddress)); +} + +TEST_F(WalletApi_commitTransaction, doesNotChangeBalanceIfTransactionSent) { + generateAndUnlockMoney(); + + std::string sourceAddress = alice.getAddress(0); + auto txId = makeTransaction({alice.getAddress(0)}, { CryptoNote::WalletOrder{ RANDOM_ADDRESS, SENT } }, FEE, 0); + waitForTransactionUpdated(alice, txId); + + uint64_t actualBefore = alice.getActualBalance(sourceAddress); + uint64_t pendingBefore = alice.getPendingBalance(sourceAddress); + + alice.commitTransaction(txId); + + waitForTransactionUpdated(alice, txId); + + EXPECT_EQ(actualBefore, alice.getActualBalance(sourceAddress)); + EXPECT_EQ(pendingBefore, alice.getPendingBalance(sourceAddress)); +} + +TEST_F(WalletApi_commitTransaction, sendsTransactionUpdatedEventIfTransactionSent) { + auto txId = generateMoneyAndMakeAliceTransaction(); + alice.commitTransaction(txId); + + ASSERT_TRUE(waitForWalletEvent(alice, WalletEventType::TRANSACTION_UPDATED, std::chrono::seconds(5))); +} + +namespace { + +class WalletApi_rollbackUncommitedTransaction : public WalletApi { +public: + WalletApi_rollbackUncommitedTransaction() : + WalletApi() { + } + +protected: + size_t generateMoneyAndMakeAliceTransaction() { + generateAndUnlockMoney(); + auto txId = makeTransaction({alice.getAddress(0)}, { CryptoNote::WalletOrder{ RANDOM_ADDRESS, SENT } }, FEE, 0); + + waitForTransactionUpdated(alice, txId); + return txId; + } + + int rollbackAliceTransactionAndReturnErrorCode(size_t transactionId) { + try { + alice.rollbackUncommitedTransaction(transactionId); + } catch (std::system_error& e) { + return e.code().value(); + } + + return 0; + } +}; + +} + +TEST_F(WalletApi_rollbackUncommitedTransaction, throwsIfStopped) { + auto txId = generateMoneyAndMakeAliceTransaction(); + alice.stop(); + ASSERT_ANY_THROW(alice.rollbackUncommitedTransaction(txId)); +} + +TEST_F(WalletApi_rollbackUncommitedTransaction, throwsIfTransactionIdIsInvalid) { + auto txId = generateMoneyAndMakeAliceTransaction(); + int error = rollbackAliceTransactionAndReturnErrorCode(txId + 1); + ASSERT_EQ(static_cast(error::WalletErrorCodes::INDEX_OUT_OF_RANGE), error); +} + +TEST_F(WalletApi_rollbackUncommitedTransaction, throwsIfTransactionIsInSucceededState) { + auto txId = generateMoneyAndMakeAliceTransaction(); + alice.commitTransaction(txId); + + int error = rollbackAliceTransactionAndReturnErrorCode(txId); + ASSERT_EQ(static_cast(error::WalletErrorCodes::TX_CANCEL_IMPOSSIBLE), error); +} + +TEST_F(WalletApi_rollbackUncommitedTransaction, rollsBackTransactionAfterFail) { + auto txId = generateMoneyAndMakeAliceTransaction(); + node.setNextTransactionError(); + ASSERT_ANY_THROW(alice.commitTransaction(txId)); + + int error = rollbackAliceTransactionAndReturnErrorCode(txId); + ASSERT_EQ(0, error); +} + +TEST_F(WalletApi_rollbackUncommitedTransaction, throwsIfTransactionIsInCancelledState) { + auto txId = generateMoneyAndMakeAliceTransaction(); + alice.rollbackUncommitedTransaction(txId); + + int error = rollbackAliceTransactionAndReturnErrorCode(txId); + ASSERT_EQ(static_cast(error::WalletErrorCodes::TX_CANCEL_IMPOSSIBLE), error); +} + +TEST_F(WalletApi_rollbackUncommitedTransaction, changesTransactionStateToCancelledIfTransactionRolledback) { + auto txId = generateMoneyAndMakeAliceTransaction(); + alice.rollbackUncommitedTransaction(txId); + auto tx = alice.getTransaction(txId); + ASSERT_EQ(WalletTransactionState::CANCELLED, tx.state); +} + +TEST_F(WalletApi_rollbackUncommitedTransaction, doesNotChangeTransactionStateToCancelledIfTransactionRolledbackFailed) { + auto txId = generateMoneyAndMakeAliceTransaction(); + alice.commitTransaction(txId); + ASSERT_ANY_THROW(alice.rollbackUncommitedTransaction(txId)); + auto tx = alice.getTransaction(txId); + ASSERT_NE(WalletTransactionState::CANCELLED, tx.state); +} + +TEST_F(WalletApi_rollbackUncommitedTransaction, unlocksMoneyIfTransactionRolledback) { + generateAndUnlockMoney(); + + std::string sourceAddress = alice.getAddress(0); + uint64_t actualBefore = alice.getActualBalance(sourceAddress); + uint64_t pendingBefore = alice.getPendingBalance(sourceAddress); + + auto txId = makeTransaction({alice.getAddress(0)}, { CryptoNote::WalletOrder{ RANDOM_ADDRESS, SENT } }, FEE, 0); + alice.rollbackUncommitedTransaction(txId); + + ASSERT_EQ(actualBefore, alice.getActualBalance(sourceAddress)); + ASSERT_EQ(pendingBefore, alice.getPendingBalance(sourceAddress)); +} + +TEST_F(WalletApi_rollbackUncommitedTransaction, doesNotChangeBalanceIfTransactionRollbackFailed) { + generateAndUnlockMoney(); + + std::string sourceAddress = alice.getAddress(0); + auto txId = makeTransaction({sourceAddress}, { CryptoNote::WalletOrder{ RANDOM_ADDRESS, SENT } }, FEE, 0); + alice.rollbackUncommitedTransaction(txId); + + uint64_t actualBefore = alice.getActualBalance(sourceAddress); + uint64_t pendingBefore = alice.getPendingBalance(sourceAddress); + ASSERT_ANY_THROW(alice.rollbackUncommitedTransaction(txId)); + + ASSERT_EQ(actualBefore, alice.getActualBalance(sourceAddress)); + ASSERT_EQ(pendingBefore, alice.getPendingBalance(sourceAddress)); +} + +TEST_F(WalletApi_rollbackUncommitedTransaction, sendsTransactionUpdatedEventIfTransactionRolledback) { + auto txId = generateMoneyAndMakeAliceTransaction(); + alice.rollbackUncommitedTransaction(txId); + + ASSERT_TRUE(waitForWalletEvent(alice, WalletEventType::TRANSACTION_UPDATED, std::chrono::seconds(5))); +} + +TEST_F(WalletApi_rollbackUncommitedTransaction, doesNotSendTransactionUpdatedEventIfTransactionRollbackFailed) { + auto txId = generateMoneyAndMakeAliceTransaction(); + alice.commitTransaction(txId); + ASSERT_TRUE(waitForWalletEvent(alice, WalletEventType::TRANSACTION_UPDATED, std::chrono::seconds(5))); + + System::Context eventContext(dispatcher, [this]() { + bool res; + + for (;;) { + try { + CryptoNote::WalletEvent event = alice.getEvent(); + if (event.type == WalletEventType::TRANSACTION_UPDATED) { + res = true; + break; + } + } catch (System::InterruptedException&) { + res = false; + break; + } + } + + return res; + }); + + ASSERT_ANY_THROW(alice.rollbackUncommitedTransaction(txId)); + + dispatcher.yield(); + eventContext.interrupt(); + ASSERT_FALSE(eventContext.get()); +} + +TEST_F(WalletApi, getTransactionThrowsIfTransactionNotFound) { + Crypto::Hash hash; + std::generate(std::begin(hash.data), std::end(hash.data), std::rand); + + ASSERT_ANY_THROW(alice.getTransaction(hash)); +} + +TEST_F(WalletApi, getTransactionThrowsIfStopped) { + alice.stop(); + + Crypto::Hash hash; + std::generate(std::begin(hash.data), std::end(hash.data), std::rand); + + ASSERT_ANY_THROW(alice.getTransaction(hash)); +} + +TEST_F(WalletApi, getTransactionThrowsIfNotInitialized) { + WalletGreen wallet(dispatcher, currency, node); + + Crypto::Hash hash; + std::generate(std::begin(hash.data), std::end(hash.data), std::rand); + + ASSERT_ANY_THROW(wallet.getTransaction(hash)); +} + +TEST_F(WalletApi, getTransactionReturnsCorrectTransaction) { + const uint64_t MONEY = 2 * SENT + 2 * FEE + 1; + + generator.getSingleOutputTransaction(parseAddress(aliceAddress), MONEY); + unlockMoney(); + + CryptoNote::TransactionParameters params; + params.destinations = { CryptoNote::WalletOrder {RANDOM_ADDRESS, SENT}, CryptoNote::WalletOrder {RANDOM_ADDRESS, SENT + FEE} }; + params.fee = FEE; + + auto txId = alice.transfer(params); + + waitForTransactionUpdated(alice, txId); //first notification comes right after inserting transaction. totalAmount at the moment is 0 + waitForTransactionUpdated(alice, txId); //second notification comes after processing the transaction by TransfersContainer + + Crypto::Hash hash = alice.getTransaction(txId).hash; + + CryptoNote::WalletTransactionWithTransfers tx = alice.getTransaction(hash); + CryptoNote::WalletTransaction transaction = tx.transaction; + + EXPECT_EQ(CryptoNote::WalletTransactionState::SUCCEEDED, transaction.state); + EXPECT_EQ(CryptoNote::WALLET_UNCONFIRMED_TRANSACTION_HEIGHT, transaction.blockHeight); + EXPECT_EQ(FEE, transaction.fee); + EXPECT_FALSE(transaction.isBase); + EXPECT_EQ(0, transaction.unlockTime); + + ASSERT_EQ(-static_cast(SENT * 2 + FEE * 2), tx.transaction.totalAmount); + + ASSERT_EQ(4, tx.transfers.size()); //2 transfers for user's orders, 1 transfer for change, 1 transfer for source + sortTransfersByAmount(tx.transfers); + + //source + EXPECT_EQ(aliceAddress, tx.transfers[0].address); + EXPECT_EQ(-static_cast(MONEY), tx.transfers[0].amount); + + //change + EXPECT_EQ(aliceAddress, tx.transfers[1].address); + EXPECT_EQ(static_cast(MONEY - 2 * SENT - 2 * FEE), tx.transfers[1].amount); + + //destinations + EXPECT_EQ(RANDOM_ADDRESS, tx.transfers[2].address); + EXPECT_EQ(static_cast(SENT), tx.transfers[2].amount); + + EXPECT_EQ(RANDOM_ADDRESS, tx.transfers[3].address); + EXPECT_EQ(static_cast(SENT + FEE), tx.transfers[3].amount); +} + +TEST_F(WalletApi, getTransactionsThrowsIfStopped) { + alice.stop(); + ASSERT_ANY_THROW(alice.getTransactions(0, 10)); + alice.start(); +} + +TEST_F(WalletApi, getTransactionsThrowsIfNotInitialized) { + WalletGreen wallet(dispatcher, currency, node); + ASSERT_ANY_THROW(wallet.getTransactions(0, 10)); +} + +TEST_F(WalletApi, getTransactionsThrowsCountZero) { + ASSERT_ANY_THROW(alice.getTransactions(0, 0)); +} + +TEST_F(WalletApi, getTransactionsReturnsEmptyArrayIfBlockIndexTooBig) { + auto transactions = alice.getTransactions(1, 1); + ASSERT_TRUE(transactions.empty()); +} + +TEST_F(WalletApi, transferDoesntAppearTwiceAfterIncludingToBlockchain) { + //we generate single output transaction to make sure we'll have change transfer in transaction + generator.getSingleOutputTransaction(parseAddress(aliceAddress), 2 * SENT + FEE); + unlockMoney(); + + CryptoNote::WalletGreen bob(dispatcher, currency, node, 1); + bob.initialize("p"); + + node.setNextTransactionToPool(); + sendMoney(bob.createAddress(), SENT, FEE); + + node.sendPoolChanged(); + + waitForTransactionCount(bob, 1); + waitForWalletEvent(bob, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + ASSERT_EQ(3, bob.getTransactionTransferCount(0)); + + node.includeTransactionsFromPoolToBlock(); + generator.generateEmptyBlocks(1); + node.updateObservers(); + waitForWalletEvent(bob, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + ASSERT_EQ(3, bob.getTransactionTransferCount(0)); +} + +TEST_F(WalletApi, incomingTransactionToTwoAddressesContainsTransfersForEachAddress) { + //we don't want to produce change + generator.getSingleOutputTransaction(parseAddress(aliceAddress), 2 * SENT + 2 * FEE); + unlockMoney(); + + CryptoNote::WalletGreen bob(dispatcher, currency, node, 1); + bob.initialize("p"); + + CryptoNote::TransactionParameters params; + params.destinations = {{bob.createAddress(), SENT}, {bob.createAddress(), SENT + FEE}}; + params.fee = FEE; + + waitForWalletEvent(bob, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + alice.transfer(params); + node.updateObservers(); + + waitForTransactionCount(bob, 1); + waitForWalletEvent(bob, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + //2 outcoming transfers to bob's addresses and one incoming for alice + ASSERT_EQ(3, bob.getTransactionTransferCount(0)); + + std::vector receivedTransfers = getTransfersFromTransaction(bob, 0, true); + std::sort(receivedTransfers.begin(), receivedTransfers.end()); + + //we expect to have exactly 2 positive transfers - one for each bob's address + ASSERT_EQ(2, receivedTransfers.size()); + + ASSERT_EQ(bob.getAddress(0), receivedTransfers[0].address); + ASSERT_EQ(SENT, receivedTransfers[0].amount); + + ASSERT_EQ(bob.getAddress(1), receivedTransfers[1].address); + ASSERT_EQ(SENT + FEE, receivedTransfers[1].amount); +} + +TEST_F(WalletApi, getTransactionsReturnsEmptyArrayIfBlockHashDoesntExist) { + Crypto::Hash hash; + std::generate(std::begin(hash.data), std::end(hash.data), std::rand); + + auto transactions = alice.getTransactions(hash, 1); + ASSERT_TRUE(transactions.empty()); +} + +TEST_F(WalletApi, getTransactionsReturnsEmptyArrayWhenNoTransactions) { + auto transactions = alice.getTransactions(0, 1); + + ASSERT_FALSE(transactions.empty()); + ASSERT_TRUE(transactions[0].transactions.empty()); +} + +bool compareTransactionsWithTransfers(CryptoNote::WalletTransactionWithTransfers& leftTransaction, CryptoNote::WalletTransactionWithTransfers& rightTransaction) { + std::sort(leftTransaction.transfers.begin(), leftTransaction.transfers.end()); + std::sort(rightTransaction.transfers.begin(), rightTransaction.transfers.end()); + + if (leftTransaction.transaction != rightTransaction.transaction) { + return false; + } + + return leftTransaction.transfers == rightTransaction.transfers; +} + +CryptoNote::WalletTransactionWithTransfers makeTransactionWithTransfers(CryptoNote::WalletGreen& wallet, size_t transactionId) { + CryptoNote::WalletTransactionWithTransfers transactionWithTransfers; + transactionWithTransfers.transaction = wallet.getTransaction(transactionId); + + for (size_t i = 0; i < wallet.getTransactionTransferCount(transactionId); ++i ) { + transactionWithTransfers.transfers.push_back(wallet.getTransactionTransfer(transactionId, i)); + } + + return transactionWithTransfers; +} + +bool transactionWithTransfersFound(CryptoNote::WalletGreen& wallet, const std::vector& transactions, size_t transactionId) { + CryptoNote::WalletTransactionWithTransfers walletTransaction = makeTransactionWithTransfers(wallet, transactionId); + + for (auto& block: transactions) { + for (auto& transaction: block.transactions) { + auto transactionCopy = transaction; + if (compareTransactionsWithTransfers(walletTransaction, transactionCopy)) { + return true; + } + } + } + + return false; +} + +size_t getTransactionsCount(const std::vector& transactions) { + size_t count = 0; + + for (auto& block: transactions) { + count += block.transactions.size(); + } + + return count; +} + +TEST_F(WalletApi, getTransactionsDoesntReturnUnconfirmedTransactions) { + generateAndUnlockMoney(); + + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + size_t transactionId = sendMoney(RANDOM_ADDRESS, SENT, FEE); + auto transactions = alice.getTransactions(0, generator.getBlockchain().size()); + + ASSERT_FALSE(transactionWithTransfersFound(alice, transactions, transactionId)); +} + +TEST_F(WalletApi, getTransactionsReturnsCorrectTransactionsFromOneBlock) { + generateAndUnlockMoney(); + const uint32_t MIXIN_1 = 1; + const uint32_t MIXIN_2 = 0; + + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + node.setNextTransactionToPool(); + size_t transactionId1 = sendMoney(RANDOM_ADDRESS, SENT, FEE, MIXIN_1); + + node.setNextTransactionToPool(); + size_t transactionId2 = sendMoney(RANDOM_ADDRESS, SENT + FEE, FEE, MIXIN_2); + + node.includeTransactionsFromPoolToBlock(); + node.updateObservers(); + + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + auto transactions = alice.getTransactions(generator.getBlockchain().size() - 1, 1); + + size_t transactionsCount = getTransactionsCount(transactions); + ASSERT_EQ(2, transactionsCount); + + ASSERT_TRUE(transactionWithTransfersFound(alice, transactions, transactionId1)); + ASSERT_TRUE(transactionWithTransfersFound(alice, transactions, transactionId2)); +} + +TEST_F(WalletApi, getTransactionsReturnsBlockWithCorrectHash) { + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + generator.generateEmptyBlocks(1); + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + Crypto::Hash lastBlockHash = get_block_hash(generator.getBlockchain().back()); + auto transactions = alice.getTransactions(lastBlockHash, 1); + + ASSERT_EQ(1, transactions.size()); + ASSERT_EQ(lastBlockHash, transactions[0].blockHash); +} + +TEST_F(WalletApi, getTransactionsReturnsCorrectTransactionByBlockHash) { + generateAndUnlockMoney(); + + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + size_t transactionId = sendMoney(RANDOM_ADDRESS, SENT, FEE); + + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + Crypto::Hash lastBlockHash = get_block_hash(generator.getBlockchain().back()); + auto transactions = alice.getTransactions(lastBlockHash, 1); + + ASSERT_TRUE(transactionWithTransfersFound(alice, transactions, transactionId)); +} + +TEST_F(WalletApi, getTransactionsDoesntReturnUnconfirmedIncomingTransactions) { + CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME); + bob.initialize("pass2"); + + generateAndUnlockMoney(); + + node.setNextTransactionToPool(); + sendMoney(bob.createAddress(), SENT, FEE); + node.updateObservers(); + + waitForTransactionCount(bob, 1); + + auto transactions = bob.getTransactions(0, generator.getBlockchain().size()); + ASSERT_EQ(0, getTransactionsCount(transactions)); + + bob.shutdown(); +} + +TEST_F(WalletApi, getTransactionsReturnsConfirmedIncomingTransactions) { + CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME); + bob.initialize("pass2"); + + generateAndUnlockMoney(); + + sendMoney(bob.createAddress(), SENT, FEE); + node.updateObservers(); + + waitForTransactionCount(bob, 1); + waitForWalletEvent(bob, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + auto transactions = bob.getTransactions(generator.getBlockchain().size() - 1, 1); + ASSERT_EQ(1, getTransactionsCount(transactions)); + ASSERT_TRUE(transactionWithTransfersFound(bob, transactions, 0)); + + bob.shutdown(); +} + +TEST_F(WalletApi, getTransactionsDoesntReturnFailedTransactions) { + generateAndUnlockMoney(); + + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + node.setNextTransactionError(); + try { + sendMoney(RANDOM_ADDRESS, SENT + FEE, FEE); + } catch (std::exception&) { + } + + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + auto transactions = alice.getTransactions(0, generator.getBlockchain().size()); + ASSERT_FALSE(transactionWithTransfersFound(alice, transactions, alice.getTransactionCount() - 1)); +} + +TEST_F(WalletApi, getTransactionsDoesntReturnDelayedTransactions) { + generateAndUnlockMoney(); + + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + size_t id = makeTransaction({}, RANDOM_ADDRESS, SENT, FEE); + + generator.generateEmptyBlocks(1); + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + auto transactions = alice.getTransactions(0, generator.getBlockchain().size()); + ASSERT_FALSE(transactionWithTransfersFound(alice, transactions, id)); +} + +TEST_F(WalletApi, getTransactionsReturnsDelayedTransactionsAfterSend) { + generateAndUnlockMoney(); + + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + size_t id = makeTransaction({}, RANDOM_ADDRESS, SENT, FEE); + alice.commitTransaction(id); + + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + auto transactions = alice.getTransactions(generator.getBlockchain().size() - 1, 1); + ASSERT_TRUE(transactionWithTransfersFound(alice, transactions, id)); +} + +TEST_F(WalletApi, getTransactionsDoesntReturnDeletedTransactions) { + generateAndUnlockMoney(); + + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + size_t detachHeight = generator.getBlockchain().size() - 1; + size_t id = sendMoney(RANDOM_ADDRESS, SENT + FEE, FEE); + + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + node.startAlternativeChain(detachHeight); + generator.generateEmptyBlocks(1); + node.updateObservers(); + + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + auto transactions = alice.getTransactions(generator.getBlockchain().size() - 1, 1); + ASSERT_FALSE(transactionWithTransfersFound(alice, transactions, id)); +} + +TEST_F(WalletApi, getTransactionsByBlockHashThrowsIfNotInitialized) { + CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME); + auto hash = get_block_hash(generator.getBlockchain().back()); + ASSERT_ANY_THROW(bob.getTransactions(hash, 1)); +} + +TEST_F(WalletApi, getTransactionsByBlockHashThrowsIfStopped) { + alice.stop(); + auto hash = get_block_hash(generator.getBlockchain().back()); + ASSERT_ANY_THROW(alice.getTransactions(hash, 1)); + alice.start(); +} + +TEST_F(WalletApi, getBlockHashesThrowsIfNotInitialized) { + CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME); + ASSERT_ANY_THROW(bob.getBlockHashes(0, 1)); +} + +TEST_F(WalletApi, getBlockHashesThrowsIfStopped) { + alice.stop(); + ASSERT_ANY_THROW(alice.getBlockHashes(0, 1)); + alice.start(); +} + +TEST_F(WalletApi, getBlockHashesReturnsEmptyVectorIfBlockIndexGreaterThanBlockhainSize) { + auto hashes = alice.getBlockHashes(1, 1); + ASSERT_TRUE(hashes.empty()); +} + +TEST_F(WalletApi, getBlockHashesReturnsNewBlocks) { + generator.generateEmptyBlocks(1); + + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + auto hash = get_block_hash(generator.getBlockchain().back()); + auto hashes = alice.getBlockHashes(0, generator.getBlockchain().size()); + + ASSERT_EQ(generator.getBlockchain().size(), hashes.size()); + ASSERT_EQ(hash, hashes.back()); +} + +TEST_F(WalletApi, getBlockHashesReturnsCorrectBlockHashesAfterDetach) { + generator.generateEmptyBlocks(1); + + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + node.startAlternativeChain(1); + generator.generateEmptyBlocks(1); + node.updateObservers(); + + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + auto hash = get_block_hash(generator.getBlockchain()[1]); + auto hashes = alice.getBlockHashes(0, 2); + + ASSERT_EQ(2, hashes.size()); + ASSERT_EQ(hash, hashes.back()); +} + +TEST_F(WalletApi, getBlockHashesReturnsOnlyGenesisBlockHashForWalletWithoutAddresses) { + CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME); + bob.initialize("pass"); + + auto hashes = bob.getBlockHashes(0, 100); + auto hash = hashes[0]; + + ASSERT_EQ(1, hashes.size()); + ASSERT_EQ(currency.genesisBlockHash(), hash); + bob.shutdown(); +} + +TEST_F(WalletApi, getBlockHashesReturnsOnlyGenesisBlockHashAfterDeletingAddresses) { + generator.generateEmptyBlocks(1); + + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + alice.deleteAddress(alice.getAddress(0)); + + auto hashes = alice.getBlockHashes(0, 100); + auto hash = hashes[0]; + + ASSERT_EQ(1, hashes.size()); + ASSERT_EQ(currency.genesisBlockHash(), hash); +} + +TEST_F(WalletApi, getBlockHashesReturnsCorrectHashesAfterLoad) { + generator.generateEmptyBlocks(1); + + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + auto hashesBefore = alice.getBlockHashes(0, generator.getBlockchain().size()); + + std::stringstream data; + alice.save(data, false, true); + + CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME); + bob.load(data, "pass"); + + auto hashesAfter = bob.getBlockHashes(0, generator.getBlockchain().size()); + ASSERT_EQ(hashesBefore, hashesAfter); + bob.shutdown(); +} + +TEST_F(WalletApi, getBlockCountThrowIfNotInitialized) { + CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME); + ASSERT_ANY_THROW(bob.getBlockCount()); +} + +TEST_F(WalletApi, getBlockCountThrowIfNotStopped) { + alice.stop(); + ASSERT_ANY_THROW(alice.getBlockCount()); + alice.start(); +} + +TEST_F(WalletApi, getBlockCountForWalletWithoutAddressesReturnsOne) { + CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME); + bob.initialize("pass"); + ASSERT_EQ(1, bob.getBlockCount()); + bob.shutdown(); +} + +TEST_F(WalletApi, getBlockCountReturnsCorrectBlockCount) { + generator.generateEmptyBlocks(1); + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + ASSERT_EQ(generator.getBlockchain().size(), alice.getBlockCount()); +} + +TEST_F(WalletApi, getBlockCountReturnsPlusOneAfterBlockAdded) { + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + auto prevBlockCount = alice.getBlockCount(); + + generator.generateEmptyBlocks(1); + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + ASSERT_EQ(prevBlockCount + 1, alice.getBlockCount()); +} + +TEST_F(WalletApi, getBlockCountReturnsCorrectBlockCountAfterDetach) { + generator.generateEmptyBlocks(2); + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + auto prevBlockCount = alice.getBlockCount(); + + auto detachBlockIndex = generator.getBlockchain().size() - 2; + node.startAlternativeChain(detachBlockIndex); + generator.generateEmptyBlocks(1); + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + ASSERT_EQ(prevBlockCount - 1, alice.getBlockCount()); +} + +TEST_F(WalletApi, getBlockCountReturnsOneAfterAddressesRemoving) { + generator.generateEmptyBlocks(1); + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + alice.deleteAddress(alice.getAddress(0)); + ASSERT_EQ(1, alice.getBlockCount()); +} + +TEST_F(WalletApi, getBlockCountReturnsCorrectBlockCountAfterLoad) { + generator.generateEmptyBlocks(1); + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + auto aliceBlockCount = alice.getBlockCount(); + + std::stringstream data; + alice.save(data, false, true); + + CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME); + ASSERT_NO_THROW(bob.load(data, "pass")); + + ASSERT_EQ(aliceBlockCount, bob.getBlockCount()); + bob.shutdown(); +} + +TEST_F(WalletApi, getUnconfirmedTransactionsThrowsIfNotInitialized) { + CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME); + ASSERT_ANY_THROW(bob.getUnconfirmedTransactions()); +} + +TEST_F(WalletApi, getUnconfirmedTransactionsThrowsIfStopped) { + alice.stop(); + ASSERT_ANY_THROW(alice.getUnconfirmedTransactions()); + alice.start(); +} + +TEST_F(WalletApi, getUnconfirmedTransactionsReturnsOneTransaction) { + generateAndUnlockMoney(); + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + CryptoNote::TransactionParameters params; + params.destinations = {{RANDOM_ADDRESS, SENT}, {RANDOM_ADDRESS, SENT + FEE}}; + params.fee = FEE; + + node.setNextTransactionToPool(); + auto transaction = makeTransactionWithTransfers(alice, alice.transfer(params)); + + auto unconfirmed = alice.getUnconfirmedTransactions(); + ASSERT_EQ(1, unconfirmed.size()); + ASSERT_TRUE(compareTransactionsWithTransfers(transaction, unconfirmed[0])); +} + +TEST_F(WalletApi, getUnconfirmedTransactionsReturnsTwoTransactions) { + generateAndUnlockMoney(); + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + node.setNextTransactionToPool(); + auto transaction1 = makeTransactionWithTransfers(alice, sendMoney(RANDOM_ADDRESS, SENT, FEE)); + + node.setNextTransactionToPool(); + auto transaction2 = makeTransactionWithTransfers(alice, sendMoney(RANDOM_ADDRESS, SENT + FEE, FEE)); + + auto unconfirmed = alice.getUnconfirmedTransactions(); + ASSERT_EQ(2, unconfirmed.size()); + + auto found1 = std::find_if(unconfirmed.begin(), unconfirmed.end(), [&transaction1] (CryptoNote::WalletTransactionWithTransfers& tr) { + return compareTransactionsWithTransfers(transaction1, tr); + }); + + ASSERT_NE(unconfirmed.end(), found1); + + auto found2 = std::find_if(unconfirmed.begin(), unconfirmed.end(), [&transaction2] (CryptoNote::WalletTransactionWithTransfers& tr) { + return compareTransactionsWithTransfers(transaction2, tr); + }); + + ASSERT_NE(unconfirmed.end(), found2); +} + +TEST_F(WalletApi, getUnconfirmedTransactionsDoesntReturnFailedTransactions) { + generateAndUnlockMoney(); + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + node.setNextTransactionError(); + try { + sendMoney(RANDOM_ADDRESS, SENT, FEE); + } catch (std::exception&) { + } + + auto unconfirmed = alice.getUnconfirmedTransactions(); + ASSERT_TRUE(unconfirmed.empty()); +} + +TEST_F(WalletApi, getUnconfirmedTransactionsDoesntReturnConfirmedTransactions) { + generateAndUnlockMoney(); + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + sendMoney(RANDOM_ADDRESS, SENT, FEE); + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + auto unconfirmed = alice.getUnconfirmedTransactions(); + ASSERT_TRUE(unconfirmed.empty()); +} + +TEST_F(WalletApi, getDelayedTransactionIdsThrowsIfNotInitialized) { + CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME); + ASSERT_ANY_THROW(bob.getDelayedTransactionIds()); +} + +TEST_F(WalletApi, getDelayedTransactionIdsThrowsIfStopped) { + alice.stop(); + ASSERT_ANY_THROW(alice.getDelayedTransactionIds()); + alice.start(); +} + +TEST_F(WalletApi, getDelayedTransactionIdsThrowsIfInTrackingMode) { + CryptoNote::WalletGreen bob(dispatcher, currency, node, TRANSACTION_SOFTLOCK_TIME); + bob.initialize("p"); + + Crypto::PublicKey pub; + Crypto::SecretKey sec; + Crypto::generate_keys(pub, sec); + + bob.createAddress(pub); + ASSERT_ANY_THROW(bob.getDelayedTransactionIds()); +} + +TEST_F(WalletApi, getDelayedTransactionIdsReturnsDelayedTransaction) { + generateAndUnlockMoney(); + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + auto id = makeTransaction({}, RANDOM_ADDRESS, SENT, FEE); + + auto delayed = alice.getDelayedTransactionIds(); + + ASSERT_EQ(1, delayed.size()); + ASSERT_EQ(id, delayed[0]); +} + +TEST_F(WalletApi, getDelayedTransactionIdsDoesntReturnSentTransactions) { + generateAndUnlockMoney(); + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + auto id = makeTransaction({}, RANDOM_ADDRESS, SENT, FEE); + alice.commitTransaction(id); + + auto delayed = alice.getDelayedTransactionIds(); + ASSERT_TRUE(delayed.empty()); +} + +TEST_F(WalletApi, getDelayedTransactionIdsDoesntReturnFailedTransactions) { + generateAndUnlockMoney(); + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::WalletEventType::SYNC_COMPLETED, std::chrono::seconds(3)); + + node.setNextTransactionError(); + try { + sendMoney(RANDOM_ADDRESS, SENT, FEE); + } catch (std::exception&){ + } + + auto delayed = alice.getDelayedTransactionIds(); + ASSERT_TRUE(delayed.empty()); +} + +TEST_F(WalletApi, transferFailsIfWrongChangeAddress) { + CryptoNote::TransactionParameters params; + params.destinations = {{RANDOM_ADDRESS, SENT}}; + params.fee = FEE; + params.changeDestination = "Wrong address"; + + ASSERT_ANY_THROW(alice.transfer(params)); +} + +TEST_F(WalletApi, transferFailsIfChangeAddressDoesntExist) { + auto changeAddress = alice.createAddress(); + + CryptoNote::TransactionParameters params; + params.destinations = {{RANDOM_ADDRESS, SENT}}; + params.fee = FEE; + params.changeDestination = changeAddress; + alice.deleteAddress(changeAddress); + + ASSERT_ANY_THROW(alice.transfer(params)); +} + +TEST_F(WalletApi, transferFailsIfChangeAddressIsNotMine) { + CryptoNote::TransactionParameters params; + params.destinations = {{RANDOM_ADDRESS, SENT}}; + params.fee = FEE; + params.changeDestination = RANDOM_ADDRESS; + + ASSERT_ANY_THROW(alice.transfer(params)); +} + +TEST_F(WalletApi, transferFailsIfWalletHasManyAddressesSourceAddressesNotSetAndNoChangeDestination) { + alice.createAddress(); + CryptoNote::TransactionParameters params; + params.destinations = {{RANDOM_ADDRESS, SENT}}; + params.fee = FEE; + + ASSERT_ANY_THROW(alice.transfer(params)); +} + +TEST_F(WalletApi, transferSendsChangeToSingleSpecifiedSourceAddress) { + const uint64_t MONEY = SENT + FEE + 1; + + alice.createAddress(); + + generator.getSingleOutputTransaction(parseAddress(alice.getAddress(1)), MONEY); + unlockMoney(); + + CryptoNote::TransactionParameters params; + params.destinations = {{RANDOM_ADDRESS, SENT}}; + params.fee = FEE; + params.sourceAddresses = {alice.getAddress(1)}; + + alice.transfer(params); + waitForActualBalance(alice, 0); + + EXPECT_EQ(MONEY - SENT - FEE, alice.getPendingBalance()); + EXPECT_EQ(MONEY - SENT - FEE, alice.getPendingBalance(alice.getAddress(1))); +} + +TEST_F(WalletApi, transferFailsIfNoChangeDestinationAndMultipleSourceAddressesSet) { + generateAndUnlockMoney(); + alice.createAddress(); + + CryptoNote::TransactionParameters params; + params.destinations = {{RANDOM_ADDRESS, SENT}}; + params.fee = FEE; + params.sourceAddresses = {aliceAddress, alice.getAddress(1)}; + + ASSERT_ANY_THROW(alice.transfer(params)); +} + +TEST_F(WalletApi, transferSendsChangeToAddress) { + const uint64_t MONEY = SENT * 3; + + generator.getSingleOutputTransaction(parseAddress(aliceAddress), MONEY); + unlockMoney(); + + CryptoNote::TransactionParameters params; + params.destinations = {{RANDOM_ADDRESS, SENT}}; + params.fee = FEE; + params.changeDestination = alice.createAddress(); + + alice.transfer(params); + node.updateObservers(); + + waitActualBalanceUpdated(MONEY); + + EXPECT_EQ(MONEY - SENT - FEE, alice.getPendingBalance()); + EXPECT_EQ(0, alice.getActualBalance()); + EXPECT_EQ(0, alice.getActualBalance(aliceAddress)); + EXPECT_EQ(0, alice.getPendingBalance(aliceAddress)); + EXPECT_EQ(0, alice.getActualBalance(alice.getAddress(1))); + EXPECT_EQ(MONEY - SENT - FEE, alice.getPendingBalance(alice.getAddress(1))); +} + +TEST_F(WalletApi, checkBaseTransaction) { + CryptoNote::AccountKeys keys{ parseAddress(alice.getAddress(0)), alice.getAddressSpendKey(0).secretKey, alice.getViewKey().secretKey }; + CryptoNote::AccountBase acc; + acc.setAccountKeys(keys); + acc.set_createtime(0); + generator.generateFromBaseTx(acc); + + node.updateObservers(); + waitForTransactionCount(alice, 1); + + ASSERT_EQ(1, alice.getTransactionCount()); + WalletTransaction tx = alice.getTransaction(0); + EXPECT_TRUE(tx.isBase); + EXPECT_EQ(0, tx.fee); + EXPECT_EQ(CryptoNote::WalletTransactionState::SUCCEEDED, tx.state); + + ASSERT_EQ(1, alice.getTransactionTransferCount(0)); + WalletTransfer transfer = alice.getTransactionTransfer(0, 0); + EXPECT_LT(0, transfer.amount); + EXPECT_EQ(tx.totalAmount, transfer.amount); +} \ No newline at end of file diff --git a/tests/UnitTests/TestWalletService.cpp b/tests/UnitTests/TestWalletService.cpp new file mode 100644 index 00000000..3c2fc482 --- /dev/null +++ b/tests/UnitTests/TestWalletService.cpp @@ -0,0 +1,1001 @@ +// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "gtest/gtest.h" + +#include +#include +#include + +#include + +#include "CryptoNoteCore/Currency.h" +#include "Logging/LoggerGroup.h" +#include "Logging/ConsoleLogger.h" +#include +#include "PaymentGate/WalletService.h" +#include "PaymentGate/WalletServiceErrorCategory.h" +#include "INodeStubs.h" +#include "Wallet/WalletErrors.h" + +using namespace CryptoNote; +using namespace PaymentService; + +namespace CryptoNote { + +bool operator== (const WalletOrder& lhs, const WalletOrder& rhs) { + return std::make_tuple(lhs.address, lhs.amount) == std::make_tuple(rhs.address, rhs.amount); +} + +bool operator== (const DonationSettings& lhs, const DonationSettings& rhs) { + return std::make_tuple(lhs.address, lhs.threshold) == std::make_tuple(rhs.address, rhs.threshold); +} + +} //namespace CryptoNote + +struct IWalletBaseStub : public CryptoNote::IWallet { + IWalletBaseStub(System::Dispatcher& dispatcher) : m_eventOccurred(dispatcher) {} + virtual ~IWalletBaseStub() {} + + virtual void initialize(const std::string& password) override { } + virtual void initializeWithViewKey(const Crypto::SecretKey& viewSecretKey, const std::string& password) override { } + virtual void load(std::istream& source, const std::string& password) override { } + virtual void shutdown() override { } + + virtual void changePassword(const std::string& oldPassword, const std::string& newPassword) override { } + virtual void save(std::ostream& destination, bool saveDetails = true, bool saveCache = true) override { } + + virtual size_t getAddressCount() const override { return 0; } + virtual std::string getAddress(size_t index) const override { return ""; } + virtual KeyPair getAddressSpendKey(size_t index) const override { return KeyPair(); } + virtual KeyPair getAddressSpendKey(const std::string& address) const override { return KeyPair(); } + virtual KeyPair getViewKey() const override { return KeyPair(); } + virtual std::string createAddress() override { return ""; } + virtual std::string createAddress(const Crypto::SecretKey& spendSecretKey) override { return ""; } + virtual std::string createAddress(const Crypto::PublicKey& spendPublicKey) override { return ""; } + virtual void deleteAddress(const std::string& address) override { } + + virtual uint64_t getActualBalance() const override { return 0; } + virtual uint64_t getActualBalance(const std::string& address) const override { return 0; } + virtual uint64_t getPendingBalance() const override { return 0; } + virtual uint64_t getPendingBalance(const std::string& address) const override { return 0; } + + virtual size_t getTransactionCount() const override { return 0; } + virtual WalletTransaction getTransaction(size_t transactionIndex) const { return WalletTransaction(); } + virtual size_t getTransactionTransferCount(size_t transactionIndex) const override { return 0; } + virtual WalletTransfer getTransactionTransfer(size_t transactionIndex, size_t transferIndex) const override { return WalletTransfer(); } + + virtual WalletTransactionWithTransfers getTransaction(const Crypto::Hash& transactionHash) const override { return WalletTransactionWithTransfers(); } + virtual std::vector getTransactions(const Crypto::Hash& blockHash, size_t count) const override { return {}; } + virtual std::vector getTransactions(uint32_t blockIndex, size_t count) const override { return {}; } + virtual std::vector getBlockHashes(uint32_t blockIndex, size_t count) const override { return {}; } + virtual uint32_t getBlockCount() const override { return 0; } + virtual std::vector getUnconfirmedTransactions() const override { return {}; } + virtual std::vector getDelayedTransactionIds() const override { return {}; } + + virtual size_t transfer(const TransactionParameters& sendingTransaction) override { return 0; } + + virtual size_t makeTransaction(const TransactionParameters& sendingTransaction) override { return 0; } + virtual void commitTransaction(size_t transactionId) override { } + virtual void rollbackUncommitedTransaction(size_t transactionId) override { } + + virtual void start() override { m_stopped = false; } + virtual void stop() override { m_stopped = true; m_eventOccurred.set(); } + + //blocks until an event occurred + virtual WalletEvent getEvent() override { + throwIfStopped(); + + while(m_events.empty()) { + m_eventOccurred.wait(); + m_eventOccurred.clear(); + throwIfStopped(); + } + + WalletEvent event = std::move(m_events.front()); + m_events.pop(); + + return event; + } + + void pushEvent(const WalletEvent& event) { + m_events.push(event); + m_eventOccurred.set(); + } + +protected: + void throwIfStopped() { + if (m_stopped) { + throw std::system_error(make_error_code(std::errc::operation_canceled)); + } + } + + bool m_stopped = false; + System::Event m_eventOccurred; + std::queue m_events; +}; + +class WalletServiceTest: public ::testing::Test { +public: + WalletServiceTest() : + currency(CryptoNote::CurrencyBuilder(logger).currency()), + generator(currency), + nodeStub(generator), + walletBase(dispatcher) + {} + + virtual void SetUp() override; +protected: + Logging::ConsoleLogger logger; + Currency currency; + TestBlockchainGenerator generator; + INodeTrivialRefreshStub nodeStub; + WalletConfiguration walletConfig; + System::Dispatcher dispatcher; + IWalletBaseStub walletBase; + + std::unique_ptr createWalletService(CryptoNote::IWallet& wallet); + std::unique_ptr createWalletService(); + Crypto::Hash generateRandomHash(); +}; + +void WalletServiceTest::SetUp() { + logger.setMaxLevel(Logging::DEBUGGING); + + walletConfig.walletFile = "test"; + walletConfig.walletPassword = "test"; +} + +std::unique_ptr WalletServiceTest::createWalletService(CryptoNote::IWallet& wallet) { + return std::unique_ptr (new WalletService(currency, dispatcher, nodeStub, wallet, walletConfig, logger)); +} + +std::unique_ptr WalletServiceTest::createWalletService() { + return createWalletService(walletBase); +} + +Crypto::Hash WalletServiceTest::generateRandomHash() { + Crypto::Hash hash; + std::generate(std::begin(hash.data), std::end(hash.data), std::rand); + return hash; +} + +class WalletServiceTest_createAddress : public WalletServiceTest { +}; + +struct WalletCreateAddressStub: public IWalletBaseStub { + WalletCreateAddressStub(System::Dispatcher& d) : IWalletBaseStub(d) {} + + virtual std::string createAddress() override { return address; } + virtual std::string createAddress(const Crypto::SecretKey& spendSecretKey) override { return address; } + virtual std::string createAddress(const Crypto::PublicKey& spendPublicKey) override { return address; } + + std::string address = "correctAddress"; +}; + +TEST_F(WalletServiceTest_createAddress, returnsCorrectAddress) { + WalletCreateAddressStub wallet(dispatcher); + + std::unique_ptr service = createWalletService(wallet); + std::string address; + std::error_code ec = service->createAddress(address); + + ASSERT_FALSE(ec); + ASSERT_EQ(wallet.address, address); +} + +TEST_F(WalletServiceTest_createAddress, invalidSecretKey) { + std::unique_ptr service = createWalletService(); + + std::string address; + std::error_code ec = service->createAddress("wrong key", address); + ASSERT_EQ(make_error_code(CryptoNote::error::WalletServiceErrorCode::WRONG_KEY_FORMAT), ec); +} + +TEST_F(WalletServiceTest_createAddress, invalidPublicKey) { + std::unique_ptr service = createWalletService(); + + std::string address; + std::error_code ec = service->createTrackingAddress("wrong key", address); + ASSERT_EQ(make_error_code(CryptoNote::error::WalletServiceErrorCode::WRONG_KEY_FORMAT), ec); +} + +TEST_F(WalletServiceTest_createAddress, correctSecretKey) { + Crypto::PublicKey pub; + Crypto::SecretKey sec; + Crypto::generate_keys(pub, sec); + + WalletCreateAddressStub wallet(dispatcher); + std::unique_ptr service = createWalletService(wallet); + + std::string address; + std::error_code ec = service->createAddress(Common::podToHex(sec), address); + + ASSERT_FALSE(ec); + ASSERT_EQ(wallet.address, address); +} + +TEST_F(WalletServiceTest_createAddress, correctPublicKey) { + Crypto::PublicKey pub; + Crypto::SecretKey sec; + Crypto::generate_keys(pub, sec); + + WalletCreateAddressStub wallet(dispatcher); + std::unique_ptr service = createWalletService(wallet); + + std::string address; + std::error_code ec = service->createTrackingAddress(Common::podToHex(pub), address); + + ASSERT_FALSE(ec); + ASSERT_EQ(wallet.address, address); +} + +class WalletServiceTest_getSpendKeys : public WalletServiceTest { +}; + +struct WalletgetSpendKeysStub: public IWalletBaseStub { + WalletgetSpendKeysStub(System::Dispatcher& d) : IWalletBaseStub(d) { + Crypto::generate_keys(keyPair.publicKey, keyPair.secretKey); + } + + virtual KeyPair getAddressSpendKey(const std::string& address) const override { + return keyPair; + } + + KeyPair keyPair; +}; + +TEST_F(WalletServiceTest_getSpendKeys, returnsKeysCorrectly) { + WalletgetSpendKeysStub wallet(dispatcher); + std::unique_ptr service = createWalletService(wallet); + + std::string publicSpendKey; + std::string secretSpendKey; + auto ec = service->getSpendkeys("address", publicSpendKey, secretSpendKey); + ASSERT_FALSE(ec); + ASSERT_EQ(Common::podToHex(wallet.keyPair.publicKey), publicSpendKey); + ASSERT_EQ(Common::podToHex(wallet.keyPair.secretKey), secretSpendKey); +} + +class WalletServiceTest_getBalance : public WalletServiceTest { +}; + +struct WalletGetBalanceStub: public IWalletBaseStub { + WalletGetBalanceStub(System::Dispatcher& d, bool byAddress) : IWalletBaseStub(d), byAddress(byAddress) { + } + + virtual uint64_t getActualBalance() const override { + if (byAddress) { + throw std::runtime_error("wrong overload"); + } + + return actualBalance; + } + + virtual uint64_t getPendingBalance() const override { + if (byAddress) { + throw std::runtime_error("wrong overload"); + } + + return pendingBalance; + } + + virtual uint64_t getActualBalance(const std::string& address) const override { + if (!byAddress) { + throw std::runtime_error("wrong overload"); + } + + return actualBalance; + } + + virtual uint64_t getPendingBalance(const std::string& address) const override { + if (!byAddress) { + throw std::runtime_error("wrong overload"); + } + + return pendingBalance; + } + + bool byAddress; + uint64_t actualBalance = 345466; + uint64_t pendingBalance = 12121; +}; + +TEST_F(WalletServiceTest_getBalance, returnsCorrectBalance) { + WalletGetBalanceStub wallet(dispatcher, false); + std::unique_ptr service = createWalletService(wallet); + + uint64_t actual; + uint64_t pending; + auto ec = service->getBalance(actual, pending); + + ASSERT_FALSE(ec); + ASSERT_EQ(wallet.actualBalance, actual); + ASSERT_EQ(wallet.pendingBalance, pending); +} + +TEST_F(WalletServiceTest_getBalance, returnsCorrectBalanceByAddress) { + WalletGetBalanceStub wallet(dispatcher, true); + std::unique_ptr service = createWalletService(wallet); + + uint64_t actual; + uint64_t pending; + auto ec = service->getBalance("address", actual, pending); + + ASSERT_FALSE(ec); + ASSERT_EQ(wallet.actualBalance, actual); + ASSERT_EQ(wallet.pendingBalance, pending); +} + +class WalletServiceTest_getBlockHashes : public WalletServiceTest { +protected: + std::vector convertBlockHashes(const std::vector& hashes) { + std::vector result; + result.reserve(hashes.size()); + + std::for_each(hashes.begin(), hashes.end(), [&result] (const std::string& str) { + Crypto::Hash hash; + Common::podFromHex(str, hash); + result.push_back(hash); + }); + + return result; + } +}; + +struct WalletGetBlockHashesStub: public IWalletBaseStub { + WalletGetBlockHashesStub(System::Dispatcher& d) : IWalletBaseStub(d) { + } + + virtual std::vector getBlockHashes(uint32_t blockIndex, size_t count) const override { + return blockHashes; + } + + std::vector blockHashes; +}; + +TEST_F(WalletServiceTest_getBlockHashes, returnsEmptyBlockHashes) { + WalletGetBlockHashesStub wallet(dispatcher); + auto service = createWalletService(wallet); + + std::vector blockHashes; + ASSERT_FALSE(service->getBlockHashes(0, 1, blockHashes)); + ASSERT_EQ(wallet.blockHashes, convertBlockHashes(blockHashes)); +} + +TEST_F(WalletServiceTest_getBlockHashes, returnsBlockHashes) { + WalletGetBlockHashesStub wallet(dispatcher); + std::generate_n(std::back_inserter(wallet.blockHashes), 10, [this] () { return generateRandomHash(); }); + auto service = createWalletService(wallet); + + std::vector blockHashes; + ASSERT_FALSE(service->getBlockHashes(0, 10, blockHashes)); + ASSERT_EQ(wallet.blockHashes, convertBlockHashes(blockHashes)); +} + +class WalletServiceTest_getViewKey : public WalletServiceTest { +}; + +struct WalletGetViewKeyStub: public IWalletBaseStub { + WalletGetViewKeyStub(System::Dispatcher& d) : IWalletBaseStub(d) { + Crypto::generate_keys(keyPair.publicKey, keyPair.secretKey); + } + + virtual KeyPair getViewKey() const override { + return keyPair; + } + + KeyPair keyPair; +}; + +TEST_F(WalletServiceTest_getViewKey, returnsCorrectValue) { + WalletGetViewKeyStub wallet(dispatcher); + auto service = createWalletService(wallet); + + std::string viewSecretKey; + ASSERT_FALSE(service->getViewKey(viewSecretKey)); + ASSERT_EQ(Common::podToHex(wallet.keyPair.secretKey), viewSecretKey); +} + +class WalletTransactionBuilder { +public: + WalletTransactionBuilder& hash(const Crypto::Hash& hash) { + transaction.hash = hash; + return *this; + } + + WalletTransactionBuilder& extra(const std::string& extra) { + transaction.extra = Common::asString(Common::fromHex(extra)); + return *this; + } + + WalletTransactionBuilder& state(WalletTransactionState state) { + transaction.state = state; + return *this; + } + + WalletTransaction build() const { + return transaction; + } + + WalletTransactionBuilder& timestamp(uint64_t t) { + transaction.timestamp = t; + return *this; + } + + WalletTransactionBuilder& blockHeight(uint32_t height) { + transaction.blockHeight = height; + return *this; + } + + WalletTransactionBuilder& totalAmount(int64_t amount) { + transaction.totalAmount = amount; + return *this; + } + + WalletTransactionBuilder& fee(uint64_t fee) { + transaction.fee = fee; + return *this; + } + + WalletTransactionBuilder& creationTime(uint64_t t) { + transaction.creationTime = t; + return *this; + } + + WalletTransactionBuilder& unlockTime(uint64_t unlock) { + transaction.unlockTime = unlock; + return *this; + } + + WalletTransactionBuilder& isBase(bool base) { + transaction.isBase = base; + return *this; + } + +private: + WalletTransaction transaction; +}; + +class WalletTransactionWithTransfersBuilder { +public: + WalletTransactionWithTransfersBuilder& transaction(const WalletTransaction& transaction) { + tx.transaction = transaction; + return *this; + } + + WalletTransactionWithTransfersBuilder& addTransfer(const std::string& address, int64_t amount) { + tx.transfers.push_back(WalletTransfer{WalletTransferType::USUAL, address, amount}); + return *this; + } + + WalletTransactionWithTransfers build() const { + return tx; + } + +private: + WalletTransactionWithTransfers tx; +}; + +class WalletServiceTest_getTransactions : public WalletServiceTest { + virtual void SetUp() override; +protected: + std::vector testTransactions; + const std::string RANDOM_ADDRESS1 = "288DiQfYSxDNQoWpR6cy94i2AWyGnxo1L1MF2ZiXg58h9P52o576CSDcJp7ZceSXSUQ7u8aTF1MigQXzAtqRZ3Uq58Sne8x"; + const std::string RANDOM_ADDRESS2 = "29PQ8VbzPi163kG59w5V8PR9A6watydfYAvwFcDS74KhDEyU9CGgqsDH719oeLbpAa4xtPsgfQ6Bv9RmKs1XZWudV6q6cmU"; + const std::string RANDOM_ADDRESS3 = "23E4CVgzJok9zXnrKzvHgbKvMXZnAgsB9FA1pkAppR6d42dWMEuJjsfcJp7ZceSXSUQ7u8aTF1MigQXzAtqRZ3Uq5AHHbzZ"; + const std::string TRANSACTION_EXTRA = "022100dededededededededededededededededededededededededededededededede"; + const std::string PAYMENT_ID = "dededededededededededededededededededededededededededededededede"; +}; + +void WalletServiceTest_getTransactions::SetUp() { + TransactionsInBlockInfo block; + block.blockHash = generateRandomHash(); + block.transactions.push_back( + WalletTransactionWithTransfersBuilder().addTransfer(RANDOM_ADDRESS1, 222).addTransfer(RANDOM_ADDRESS2, 33333).transaction( + WalletTransactionBuilder().hash(generateRandomHash()).extra(TRANSACTION_EXTRA).build() + ).build() + ); + + testTransactions.push_back(block); +} + +class WalletGetTransactionsStub : public IWalletBaseStub { +public: + WalletGetTransactionsStub(System::Dispatcher& d) : IWalletBaseStub(d) {} + virtual std::vector getTransactions(const Crypto::Hash& blockHash, size_t count) const override { + return transactions; + } + + virtual std::vector getTransactions(uint32_t blockIndex, size_t count) const override { + return transactions; + } + + std::vector transactions; +}; + +TEST_F(WalletServiceTest_getTransactions, addressesFilter_emptyReturnsTransaction) { + WalletGetTransactionsStub wallet(dispatcher); + wallet.transactions = testTransactions; + + auto service = createWalletService(wallet); + + std::vector transactions; + auto ec = service->getTransactions({}, 0, 1, "", transactions); + + ASSERT_FALSE(ec); + + ASSERT_EQ(1, transactions.size()); + ASSERT_EQ(Common::podToHex(testTransactions[0].transactions[0].transaction.hash), transactions[0].transactions[0].transactionHash); +} + +TEST_F(WalletServiceTest_getTransactions, addressesFilter_existentReturnsTransaction) { + WalletGetTransactionsStub wallet(dispatcher); + wallet.transactions = testTransactions; + + auto service = createWalletService(wallet); + + std::vector transactions; + auto ec = service->getTransactions({RANDOM_ADDRESS1}, 0, 1, "", transactions); + + ASSERT_FALSE(ec); + + ASSERT_EQ(1, transactions.size()); + ASSERT_EQ(Common::podToHex(testTransactions[0].transactions[0].transaction.hash), transactions[0].transactions[0].transactionHash); +} + +TEST_F(WalletServiceTest_getTransactions, addressesFilter_nonExistentReturnsNoTransactions) { + WalletGetTransactionsStub wallet(dispatcher); + wallet.transactions = testTransactions; + + auto service = createWalletService(wallet); + + std::vector transactions; + auto ec = service->getTransactions({RANDOM_ADDRESS3}, 0, 1, "", transactions); + + ASSERT_FALSE(ec); + + ASSERT_EQ(1, transactions.size()); + ASSERT_TRUE(transactions[0].transactions.empty()); +} + +TEST_F(WalletServiceTest_getTransactions, addressesFilter_existentAndNonExistentReturnsTransaction) { + WalletGetTransactionsStub wallet(dispatcher); + wallet.transactions = testTransactions; + + auto service = createWalletService(wallet); + + std::vector transactions; + auto ec = service->getTransactions({RANDOM_ADDRESS1, RANDOM_ADDRESS3}, 0, 1, "", transactions); + + ASSERT_FALSE(ec); + + ASSERT_EQ(1, transactions.size()); + ASSERT_EQ(Common::podToHex(testTransactions[0].transactions[0].transaction.hash), transactions[0].transactions[0].transactionHash); +} + +TEST_F(WalletServiceTest_getTransactions, paymentIdFilter_existentReturnsTransaction) { + WalletGetTransactionsStub wallet(dispatcher); + wallet.transactions = testTransactions; + + auto service = createWalletService(wallet); + + std::vector transactions; + auto ec = service->getTransactions({}, 0, 1, PAYMENT_ID, transactions); + + ASSERT_FALSE(ec); + + ASSERT_EQ(1, transactions.size()); + ASSERT_EQ(Common::podToHex(testTransactions[0].transactions[0].transaction.hash), transactions[0].transactions[0].transactionHash); + ASSERT_EQ(PAYMENT_ID, transactions[0].transactions[0].paymentId); +} + +TEST_F(WalletServiceTest_getTransactions, paymentIdFilter_nonExistentReturnsNoTransaction) { + WalletGetTransactionsStub wallet(dispatcher); + wallet.transactions = testTransactions; + + auto service = createWalletService(wallet); + + std::vector transactions; + auto ec = service->getTransactions({}, 0, 1, "dfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdf", transactions); + + ASSERT_FALSE(ec); + + ASSERT_EQ(1, transactions.size()); + ASSERT_TRUE(transactions[0].transactions.empty()); +} + +TEST_F(WalletServiceTest_getTransactions, invalidAddress) { + WalletGetTransactionsStub wallet(dispatcher); + wallet.transactions = testTransactions; + + auto service = createWalletService(wallet); + + std::vector transactions; + auto ec = service->getTransactions({"invalid address"}, 0, 1, "", transactions); + ASSERT_EQ(make_error_code(CryptoNote::error::BAD_ADDRESS), ec); +} + +TEST_F(WalletServiceTest_getTransactions, invalidPaymentId) { + WalletGetTransactionsStub wallet(dispatcher); + wallet.transactions = testTransactions; + + auto service = createWalletService(wallet); + + std::vector transactions; + auto ec = service->getTransactions({}, 0, 1, "invalid payment id", transactions); + ASSERT_EQ(make_error_code(CryptoNote::error::WalletServiceErrorCode::WRONG_PAYMENT_ID_FORMAT), ec); +} + +TEST_F(WalletServiceTest_getTransactions, blockNotFound) { + WalletGetTransactionsStub wallet(dispatcher); + auto service = createWalletService(wallet); + std::vector transactions; + + auto ec = service->getTransactions({}, 0, 1, "", transactions); + ASSERT_EQ(make_error_code(CryptoNote::error::WalletServiceErrorCode::OBJECT_NOT_FOUND), ec); +} + +class WalletServiceTest_getTransaction : public WalletServiceTest_getTransactions { +}; + +struct WalletGetTransactionStub : public IWalletBaseStub { + WalletGetTransactionStub (System::Dispatcher& dispatcher) : IWalletBaseStub(dispatcher) { + } + + virtual WalletTransactionWithTransfers getTransaction(const Crypto::Hash& transactionHash) const override { + return transaction; + } + + WalletTransactionWithTransfers transaction; +}; + +TEST_F(WalletServiceTest_getTransaction, wrongHash) { + auto service = createWalletService(); + + TransactionRpcInfo transaction; + auto ec = service->getTransaction("wrong hash", transaction); + ASSERT_EQ(make_error_code(CryptoNote::error::WalletServiceErrorCode::WRONG_HASH_FORMAT), ec); +} + +TEST_F(WalletServiceTest_getTransaction, returnsCorrectFields) { + WalletGetTransactionStub wallet(dispatcher); + wallet.transaction = WalletTransactionWithTransfersBuilder().transaction( + WalletTransactionBuilder() + .state(WalletTransactionState::FAILED) + .hash(generateRandomHash()) + .creationTime(789123) + .extra(TRANSACTION_EXTRA) + .fee(293945) + .isBase(false) + .timestamp(929293847) + .totalAmount(-200000) + .unlockTime(23456) + .build() + ).addTransfer("address1", 231).addTransfer("address2", 883).build(); + + auto service = createWalletService(wallet); + + TransactionRpcInfo transaction; + auto ec = service->getTransaction(Common::podToHex(Crypto::Hash()), transaction); + + ASSERT_FALSE(ec); + ASSERT_EQ(static_cast(wallet.transaction.transaction.state), transaction.state); + ASSERT_EQ(wallet.transaction.transaction.blockHeight, transaction.blockIndex); + ASSERT_EQ(Common::toHex(Common::asBinaryArray(wallet.transaction.transaction.extra)), transaction.extra); + ASSERT_EQ(PAYMENT_ID, transaction.paymentId); + ASSERT_EQ(wallet.transaction.transaction.fee, transaction.fee); + ASSERT_EQ(wallet.transaction.transaction.isBase, transaction.isBase); + ASSERT_EQ(wallet.transaction.transaction.timestamp, transaction.timestamp); + ASSERT_EQ(Common::podToHex(wallet.transaction.transaction.hash), transaction.transactionHash); + ASSERT_EQ(wallet.transaction.transaction.unlockTime, transaction.unlockTime); + + ASSERT_EQ(wallet.transaction.transfers.size(), transaction.transfers.size()); + + ASSERT_EQ(wallet.transaction.transfers[0].address, transaction.transfers[0].address); + ASSERT_EQ(wallet.transaction.transfers[0].amount, transaction.transfers[0].amount); + + ASSERT_EQ(wallet.transaction.transfers[1].address, transaction.transfers[1].address); + ASSERT_EQ(wallet.transaction.transfers[1].amount, transaction.transfers[1].amount); + +} + +struct WalletGetTransactionThrowStub : public IWalletBaseStub { + WalletGetTransactionThrowStub (System::Dispatcher& dispatcher) : IWalletBaseStub(dispatcher) { + } + + virtual WalletTransactionWithTransfers getTransaction(const Crypto::Hash& transactionHash) const override { + throw std::system_error(make_error_code(error::OBJECT_NOT_FOUND)); + } +}; + +TEST_F(WalletServiceTest_getTransaction, transactionNotFound) { + WalletGetTransactionThrowStub wallet(dispatcher); + auto service = createWalletService(wallet); + + TransactionRpcInfo transaction; + auto ec = service->getTransaction(Common::podToHex(Crypto::Hash()), transaction); + + ASSERT_EQ(make_error_code(error::OBJECT_NOT_FOUND), ec); +} + +class WalletServiceTest_sendTransaction : public WalletServiceTest_getTransactions { + virtual void SetUp() override; +protected: + SendTransaction::Request request; +}; + +void WalletServiceTest_sendTransaction::SetUp() { + request.sourceAddresses.insert(request.sourceAddresses.end(), {RANDOM_ADDRESS1, RANDOM_ADDRESS2}); + request.transfers.push_back(WalletRpcOrder {RANDOM_ADDRESS3, 11111}); + request.fee = 2021; + request.anonymity = 4; + request.unlockTime = 848309; +} + +struct WalletTransferStub : public IWalletBaseStub { + WalletTransferStub(System::Dispatcher& dispatcher, const Crypto::Hash& hash) : IWalletBaseStub(dispatcher), hash(hash) { + } + + virtual size_t transfer(const TransactionParameters& sendingTransaction) override { + params = sendingTransaction; + return 0; + } + + virtual WalletTransaction getTransaction(size_t transactionIndex) const override { + return WalletTransactionBuilder().hash(hash).build(); + } + + Crypto::Hash hash; + TransactionParameters params; +}; + +bool isEquivalent(const SendTransaction::Request& request, const TransactionParameters& params) { + std::string extra; + if (!request.paymentId.empty()) { + extra = "022100" + request.paymentId; + } else { + extra = request.extra; + } + + std::vector orders; + std::for_each(request.transfers.begin(), request.transfers.end(), [&orders] (const WalletRpcOrder& order) { + orders.push_back( WalletOrder{order.address, order.amount}); + }); + + return std::make_tuple(request.sourceAddresses, orders, request.fee, request.anonymity, extra, request.unlockTime) + == + std::make_tuple(params.sourceAddresses, params.destinations, params.fee, params.mixIn, Common::toHex(Common::asBinaryArray(params.extra)), params.unlockTimestamp); +} + +TEST_F(WalletServiceTest_sendTransaction, passesCorrectParameters) { + WalletTransferStub wallet(dispatcher, generateRandomHash()); + auto service = createWalletService(wallet); + + std::string hash; + auto ec = service->sendTransaction(request, hash); + + ASSERT_FALSE(ec); + ASSERT_EQ(Common::podToHex(wallet.hash), hash); + ASSERT_TRUE(isEquivalent(request, wallet.params)); +} + +TEST_F(WalletServiceTest_sendTransaction, incorrectSourceAddress) { + auto service = createWalletService(); + request.sourceAddresses.push_back("wrong address"); + + std::string hash; + auto ec = service->sendTransaction(request, hash); + ASSERT_EQ(make_error_code(CryptoNote::error::BAD_ADDRESS), ec); +} + +TEST_F(WalletServiceTest_sendTransaction, incorrectTransferAddress) { + auto service = createWalletService(); + request.transfers.push_back(WalletRpcOrder{"wrong address", 12131}); + + std::string hash; + auto ec = service->sendTransaction(request, hash); + ASSERT_EQ(make_error_code(CryptoNote::error::BAD_ADDRESS), ec); +} + +class WalletServiceTest_createDelayedTransaction : public WalletServiceTest_getTransactions { + virtual void SetUp() override; +protected: + CreateDelayedTransaction::Request request; +}; + +void WalletServiceTest_createDelayedTransaction::SetUp() { + request.addresses.insert(request.addresses.end(), {RANDOM_ADDRESS1, RANDOM_ADDRESS2}); + request.transfers.push_back(WalletRpcOrder {RANDOM_ADDRESS3, 11111}); + request.fee = 2021; + request.anonymity = 4; + request.unlockTime = 848309; +} + +struct WalletMakeTransactionStub : public IWalletBaseStub { + WalletMakeTransactionStub(System::Dispatcher& dispatcher, const Crypto::Hash& hash) : IWalletBaseStub(dispatcher), hash(hash) { + } + + virtual size_t makeTransaction(const TransactionParameters& sendingTransaction) override { + params = sendingTransaction; + return 0; + } + + virtual WalletTransaction getTransaction(size_t transactionIndex) const override { + return WalletTransactionBuilder().hash(hash).build(); + } + + Crypto::Hash hash; + TransactionParameters params; +}; + +bool isEquivalent(const CreateDelayedTransaction::Request& request, const TransactionParameters& params) { + std::string extra; + if (!request.paymentId.empty()) { + extra = "022100" + request.paymentId; + } else { + extra = request.extra; + } + + std::vector orders; + std::for_each(request.transfers.begin(), request.transfers.end(), [&orders] (const WalletRpcOrder& order) { + orders.push_back( WalletOrder{order.address, order.amount}); + }); + + return std::make_tuple(request.addresses, orders, request.fee, request.anonymity, extra, request.unlockTime) + == + std::make_tuple(params.sourceAddresses, params.destinations, params.fee, params.mixIn, Common::toHex(Common::asBinaryArray(params.extra)), params.unlockTimestamp); +} + +TEST_F(WalletServiceTest_createDelayedTransaction, passesCorrectParameters) { + WalletMakeTransactionStub wallet(dispatcher, generateRandomHash()); + auto service = createWalletService(wallet); + + std::string hash; + auto ec = service->createDelayedTransaction(request, hash); + + ASSERT_FALSE(ec); + ASSERT_EQ(Common::podToHex(wallet.hash), hash); + ASSERT_TRUE(isEquivalent(request, wallet.params)); +} + +TEST_F(WalletServiceTest_createDelayedTransaction, incorrectSourceAddress) { + auto service = createWalletService(); + request.addresses.push_back("wrong address"); + + std::string hash; + auto ec = service->createDelayedTransaction(request, hash); + ASSERT_EQ(make_error_code(CryptoNote::error::BAD_ADDRESS), ec); +} + +TEST_F(WalletServiceTest_createDelayedTransaction, incorrectTransferAddress) { + auto service = createWalletService(); + request.transfers.push_back(WalletRpcOrder{"wrong address", 12131}); + + std::string hash; + auto ec = service->createDelayedTransaction(request, hash); + ASSERT_EQ(make_error_code(CryptoNote::error::BAD_ADDRESS), ec); +} + +class WalletServiceTest_getDelayedTransactionHashes: public WalletServiceTest { +}; + +struct WalletGetDelayedTransactionIdsStub : public IWalletBaseStub { + WalletGetDelayedTransactionIdsStub(System::Dispatcher& dispatcher, const Crypto::Hash& hash) : IWalletBaseStub(dispatcher), hash(hash) { + } + + virtual std::vector getDelayedTransactionIds() const override { + return {0}; + } + + virtual WalletTransaction getTransaction(size_t transactionIndex) const { + return WalletTransactionBuilder().hash(hash).build(); + } + + const Crypto::Hash hash; +}; + +TEST_F(WalletServiceTest_getDelayedTransactionHashes, returnsCorrectResult) { + WalletGetDelayedTransactionIdsStub wallet(dispatcher, generateRandomHash()); + auto service = createWalletService(wallet); + + std::vector hashes; + auto ec = service->getDelayedTransactionHashes(hashes); + + ASSERT_FALSE(ec); + ASSERT_EQ(1, hashes.size()); + ASSERT_EQ(Common::podToHex(wallet.hash), hashes[0]); +} + +class WalletServiceTest_getUnconfirmedTransactionHashes: public WalletServiceTest_getTransactions { +public: + virtual void SetUp() override; +protected: + std::vector transactions; +}; + +void WalletServiceTest_getUnconfirmedTransactionHashes::SetUp() { + transactions = { WalletTransactionWithTransfersBuilder().transaction( + WalletTransactionBuilder().hash(generateRandomHash()).build() + ).addTransfer(RANDOM_ADDRESS1, 100).addTransfer(RANDOM_ADDRESS2, 333).build() + , + WalletTransactionWithTransfersBuilder().transaction( + WalletTransactionBuilder().hash(generateRandomHash()).build() + ).addTransfer(RANDOM_ADDRESS3, 123).addTransfer(RANDOM_ADDRESS2, 4252).build() + }; +} + +struct WalletGetUnconfirmedTransactionsStub : public IWalletBaseStub { + WalletGetUnconfirmedTransactionsStub(System::Dispatcher& dispatcher) : IWalletBaseStub(dispatcher) { + } + + virtual std::vector getUnconfirmedTransactions() const override { + return transactions; + } + + std::vector transactions; +}; + +TEST_F(WalletServiceTest_getUnconfirmedTransactionHashes, returnsAllHashesWithoutAddresses) { + WalletGetUnconfirmedTransactionsStub wallet(dispatcher); + wallet.transactions = transactions; + auto service = createWalletService(wallet); + + std::vector hashes; + auto ec = service->getUnconfirmedTransactionHashes({}, hashes); + + ASSERT_FALSE(ec); + ASSERT_EQ(2, hashes.size()); + ASSERT_EQ(hashes[0], Common::podToHex(transactions[0].transaction.hash)); + ASSERT_EQ(hashes[1], Common::podToHex(transactions[1].transaction.hash)); +} + +TEST_F(WalletServiceTest_getUnconfirmedTransactionHashes, returnsOneTransactionWithAddressFilter) { + WalletGetUnconfirmedTransactionsStub wallet(dispatcher); + wallet.transactions = transactions; + auto service = createWalletService(wallet); + + std::vector hashes; + auto ec = service->getUnconfirmedTransactionHashes({RANDOM_ADDRESS1}, hashes); + + ASSERT_FALSE(ec); + ASSERT_EQ(1, hashes.size()); + ASSERT_EQ(hashes[0], Common::podToHex(transactions[0].transaction.hash)); +} + +TEST_F(WalletServiceTest_getUnconfirmedTransactionHashes, returnsTwoTransactionsWithAddressFilter) { + WalletGetUnconfirmedTransactionsStub wallet(dispatcher); + wallet.transactions = transactions; + auto service = createWalletService(wallet); + + std::vector hashes; + auto ec = service->getUnconfirmedTransactionHashes({RANDOM_ADDRESS2}, hashes); + + ASSERT_FALSE(ec); + ASSERT_EQ(2, hashes.size()); + ASSERT_EQ(hashes[0], Common::podToHex(transactions[0].transaction.hash)); + ASSERT_EQ(hashes[1], Common::podToHex(transactions[1].transaction.hash)); +} + +TEST_F(WalletServiceTest_getUnconfirmedTransactionHashes, wrongAddressFilter) { + auto service = createWalletService(); + + std::vector hashes; + auto ec = service->getUnconfirmedTransactionHashes({"wrong address"}, hashes); + + ASSERT_EQ(make_error_code(CryptoNote::error::BAD_ADDRESS), ec); +}