diff --git a/include/IWallet.h b/include/IWallet.h index 854f0dfd..d10db02c 100755 --- a/include/IWallet.h +++ b/include/IWallet.h @@ -37,7 +37,9 @@ enum class WalletTransactionState : uint8_t { enum WalletEventType { TRANSACTION_CREATED, TRANSACTION_UPDATED, - BALANCE_UNLOCKED + BALANCE_UNLOCKED, + SYNC_PROGRESS_UPDATED, + SYNC_COMPLETED }; struct WalletTransactionCreatedData { @@ -48,11 +50,17 @@ struct WalletTransactionUpdatedData { size_t transactionIndex; }; +struct WalletSynchronizationProgressUpdated { + uint32_t processedBlockCount; + uint32_t totalBlockCount; +}; + struct WalletEvent { WalletEventType type; union { WalletTransactionCreatedData transactionCreated; WalletTransactionUpdatedData transactionUpdated; + WalletSynchronizationProgressUpdated synchronizationProgressUpdated; }; }; @@ -78,6 +86,7 @@ public: virtual ~IWallet() {} virtual void initialize(const std::string& password) = 0; + virtual void initializeWithViewKey(const Crypto::SecretKey& viewSecretKey, const std::string& password) = 0; virtual void load(std::istream& source, const std::string& password) = 0; virtual void shutdown() = 0; @@ -86,8 +95,10 @@ 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 getViewKey() const = 0; virtual std::string createAddress() = 0; - virtual std::string createAddress(const KeyPair& spendKey) = 0; + virtual std::string createAddress(const Crypto::SecretKey& spendSecretKey) = 0; virtual void deleteAddress(const std::string& address) = 0; virtual uint64_t getActualBalance() const = 0; diff --git a/src/CryptoNoteConfig.h b/src/CryptoNoteConfig.h index b7cfc4e4..3f988e15 100644 --- a/src/CryptoNoteConfig.h +++ b/src/CryptoNoteConfig.h @@ -161,7 +161,8 @@ const CheckpointData CHECKPOINTS[] = { {789000, "acef490bbccce3b7b7ae8554a414f55413fbf4ca1472c6359b126a4439bd9f01"}, {796000, "04e387a00d35db21d4d93d04040b31f22573972a7e61d72cc07d0ab69bcb9c44"}, {800000, "d7fa4eea02e5ce60b949136569c0ea7ac71ea46e0065311054072ac415560b86"}, - {804000, "bcc8b3782499aae508c40d5587d1cc5d68281435ea9bfc6804a262047f7b934d"} + {804000, "bcc8b3782499aae508c40d5587d1cc5d68281435ea9bfc6804a262047f7b934d"}, + {810500, "302b2349f221232820adc3dadafd8a61b035491e33af669c78a687949eb0a381"} }; } // CryptoNote diff --git a/src/CryptoNoteCore/CoreConfig.cpp b/src/CryptoNoteCore/CoreConfig.cpp index 9bcf5239..96b12c63 100755 --- a/src/CryptoNoteCore/CoreConfig.cpp +++ b/src/CryptoNoteCore/CoreConfig.cpp @@ -27,7 +27,9 @@ CoreConfig::CoreConfig() { } void CoreConfig::init(const boost::program_options::variables_map& options) { - configFolder = command_line::get_arg(options, command_line::arg_data_dir); + 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); + } } void CoreConfig::initOptions(boost::program_options::options_description& desc) { diff --git a/src/NodeRpcProxy/NodeErrors.h b/src/NodeRpcProxy/NodeErrors.h index d030b211..01688521 100644 --- a/src/NodeRpcProxy/NodeErrors.h +++ b/src/NodeRpcProxy/NodeErrors.h @@ -30,7 +30,8 @@ enum NodeErrorCodes { NETWORK_ERROR, NODE_BUSY, INTERNAL_NODE_ERROR, - REQUEST_ERROR + REQUEST_ERROR, + CONNECT_ERROR }; // custom category: @@ -54,6 +55,7 @@ public: case NODE_BUSY: return "Node is busy"; case INTERNAL_NODE_ERROR: return "Internal node error"; case REQUEST_ERROR: return "Error in request parameters"; + case CONNECT_ERROR: return "Can't connect to daemon"; default: return "Unknown error"; } } diff --git a/src/NodeRpcProxy/NodeRpcProxy.cpp b/src/NodeRpcProxy/NodeRpcProxy.cpp index bc94b35b..f6d0e917 100644 --- a/src/NodeRpcProxy/NodeRpcProxy.cpp +++ b/src/NodeRpcProxy/NodeRpcProxy.cpp @@ -66,7 +66,8 @@ NodeRpcProxy::NodeRpcProxy(const std::string& nodeHost, unsigned short nodePort) m_pullInterval(5000), m_nodeHost(nodeHost), m_nodePort(nodePort), - m_lastLocalBlockTimestamp(0) { + m_lastLocalBlockTimestamp(0), + m_connected(true) { resetInternalState(); } @@ -170,6 +171,8 @@ void NodeRpcProxy::workerThread(const INode::Callback& initialized_callback) { m_context_group = nullptr; m_httpClient = nullptr; m_httpEvent = nullptr; + m_connected = false; + m_rpcProxyObserverManager.notify(&INodeRpcProxyObserver::connectionStatusUpdated, m_connected); } void NodeRpcProxy::updateNodeStatus() { @@ -200,6 +203,10 @@ void NodeRpcProxy::updateNodeStatus() { } updatePeerCount(); + if (m_connected != m_httpClient->isConnected()) { + m_connected = m_httpClient->isConnected(); + m_rpcProxyObserverManager.notify(&INodeRpcProxyObserver::connectionStatusUpdated, m_connected); + } } void NodeRpcProxy::updatePeerCount() { @@ -225,6 +232,14 @@ bool NodeRpcProxy::removeObserver(INodeObserver* observer) { return m_observerManager.remove(observer); } +bool NodeRpcProxy::addObserver(CryptoNote::INodeRpcProxyObserver* observer) { + return m_rpcProxyObserverManager.add(observer); +} + +bool NodeRpcProxy::removeObserver(CryptoNote::INodeRpcProxyObserver* observer) { + return m_rpcProxyObserverManager.remove(observer); +} + size_t NodeRpcProxy::getPeerCount() const { return m_peerCount.load(std::memory_order_relaxed); } @@ -563,6 +578,10 @@ void NodeRpcProxy::scheduleRequest(std::function&& procedure, callback(std::make_error_code(std::errc::operation_canceled)); } else { std::error_code ec = procedure(); + if (m_connected != m_httpClient->isConnected()) { + m_connected = m_httpClient->isConnected(); + m_rpcProxyObserverManager.notify(&INodeRpcProxyObserver::connectionStatusUpdated, m_connected); + } callback(m_stop ? std::make_error_code(std::errc::operation_canceled) : ec); } }, std::move(procedure), std::move(callback))); @@ -577,6 +596,8 @@ std::error_code NodeRpcProxy::binaryCommand(const std::string& url, const Reques EventLock eventLock(*m_httpEvent); invokeBinaryCommand(*m_httpClient, url, req, res); ec = interpretResponseStatus(res.status); + } catch (const ConnectException&) { + ec = make_error_code(error::CONNECT_ERROR); } catch (const std::exception&) { ec = make_error_code(error::NETWORK_ERROR); } @@ -592,6 +613,8 @@ std::error_code NodeRpcProxy::jsonCommand(const std::string& url, const Request& EventLock eventLock(*m_httpEvent); invokeJsonCommand(*m_httpClient, url, req, res); ec = interpretResponseStatus(res.status); + } catch (const ConnectException&) { + ec = make_error_code(error::CONNECT_ERROR); } catch (const std::exception&) { ec = make_error_code(error::NETWORK_ERROR); } @@ -627,6 +650,8 @@ std::error_code NodeRpcProxy::jsonRpcCommand(const std::string& method, const Re ec = interpretResponseStatus(res.status); } } + } catch (const ConnectException&) { + ec = make_error_code(error::CONNECT_ERROR); } catch (const std::exception&) { ec = make_error_code(error::NETWORK_ERROR); } diff --git a/src/NodeRpcProxy/NodeRpcProxy.h b/src/NodeRpcProxy/NodeRpcProxy.h index 8b5674c0..0cbfd9a8 100644 --- a/src/NodeRpcProxy/NodeRpcProxy.h +++ b/src/NodeRpcProxy/NodeRpcProxy.h @@ -36,6 +36,12 @@ namespace CryptoNote { class HttpClient; +class INodeRpcProxyObserver { +public: + virtual ~INodeRpcProxyObserver() {} + virtual void connectionStatusUpdated(bool connected) {} +}; + class NodeRpcProxy : public CryptoNote::INode { public: NodeRpcProxy(const std::string& nodeHost, unsigned short nodePort); @@ -44,6 +50,9 @@ public: virtual bool addObserver(CryptoNote::INodeObserver* observer); virtual bool removeObserver(CryptoNote::INodeObserver* observer); + virtual bool addObserver(CryptoNote::INodeRpcProxyObserver* observer); + virtual bool removeObserver(CryptoNote::INodeRpcProxyObserver* observer); + virtual void init(const Callback& callback); virtual bool shutdown(); @@ -116,6 +125,7 @@ private: System::Dispatcher* m_dispatcher = nullptr; System::ContextGroup* m_context_group = nullptr; Tools::ObserverManager m_observerManager; + Tools::ObserverManager m_rpcProxyObserverManager; const std::string m_nodeHost; const unsigned short m_nodePort; @@ -134,6 +144,8 @@ private: //protect it with mutex if decided to add worker threads Crypto::Hash m_lastKnowHash; std::atomic m_lastLocalBlockTimestamp; + + bool m_connected; }; } diff --git a/src/P2p/ConnectionContext.h b/src/P2p/ConnectionContext.h index 1550e969..83026c82 100755 --- a/src/P2p/ConnectionContext.h +++ b/src/P2p/ConnectionContext.h @@ -64,6 +64,8 @@ inline std::string get_protocol_state_string(CryptoNoteConnectionContext::state return "state_normal"; case CryptoNoteConnectionContext::state_sync_required: return "state_sync_required"; + case CryptoNoteConnectionContext::state_pool_sync_required: + return "state_pool_sync_required"; case CryptoNoteConnectionContext::state_shutdown: return "state_shutdown"; default: diff --git a/src/P2p/LevinProtocol.cpp b/src/P2p/LevinProtocol.cpp index ccecdd5f..f12873cc 100644 --- a/src/P2p/LevinProtocol.cpp +++ b/src/P2p/LevinProtocol.cpp @@ -68,13 +68,13 @@ void LevinProtocol::sendMessage(uint32_t command, const BinaryArray& out, bool n stream.writeSome(&head, sizeof(head)); stream.writeSome(out.data(), out.size()); - m_conn.write(writeBuffer.data(), writeBuffer.size()); + writeStrict(writeBuffer.data(), writeBuffer.size()); } bool LevinProtocol::readCommand(Command& cmd) { bucket_head2 head = { 0 }; - if (!readStrict(&head, sizeof(head))) { + if (!readStrict(reinterpret_cast(&head), sizeof(head))) { return false; } @@ -113,18 +113,27 @@ void LevinProtocol::sendReply(uint32_t command, const BinaryArray& out, int32_t head.m_flags = LEVIN_PACKET_RESPONSE; head.m_return_code = returnCode; - m_conn.write(reinterpret_cast(&head), sizeof(head)); - if (out.size() > 0) { - m_conn.write(reinterpret_cast(out.data()), out.size()); + BinaryArray writeBuffer; + writeBuffer.reserve(sizeof(head) + out.size()); + + Common::VectorOutputStream stream(writeBuffer); + stream.writeSome(&head, sizeof(head)); + stream.writeSome(out.data(), out.size()); + + writeStrict(writeBuffer.data(), writeBuffer.size()); +} + +void LevinProtocol::writeStrict(const uint8_t* ptr, size_t size) { + size_t offset = 0; + while (offset < size) { + offset += m_conn.write(ptr + offset, size - offset); } } -bool LevinProtocol::readStrict(void* ptr, size_t size) { - char* pos = reinterpret_cast(ptr); +bool LevinProtocol::readStrict(uint8_t* ptr, size_t size) { size_t offset = 0; - while (offset < size) { - size_t read = m_conn.read(reinterpret_cast(pos + offset), size - offset); + size_t read = m_conn.read(ptr + offset, size - offset); if (read == 0) { return false; } diff --git a/src/P2p/LevinProtocol.h b/src/P2p/LevinProtocol.h index 37f394da..6334639d 100755 --- a/src/P2p/LevinProtocol.h +++ b/src/P2p/LevinProtocol.h @@ -105,7 +105,8 @@ public: private: - bool readStrict(void* ptr, size_t size); + bool readStrict(uint8_t* ptr, size_t size); + void writeStrict(const uint8_t* ptr, size_t size); System::TcpConnection& m_conn; }; diff --git a/src/P2p/NetNode.cpp b/src/P2p/NetNode.cpp index 106f0b41..52882a0a 100644 --- a/src/P2p/NetNode.cpp +++ b/src/P2p/NetNode.cpp @@ -731,7 +731,8 @@ namespace CryptoNote }); System::Context<> timeoutContext(m_dispatcher, [&] { - System::Timer(m_dispatcher).sleep(std::chrono::milliseconds(m_config.m_net_config.connection_timeout)); + // Here we use connection_timeout * 3, one for this handshake, and two for back ping from peer. + System::Timer(m_dispatcher).sleep(std::chrono::milliseconds(m_config.m_net_config.connection_timeout * 3)); handshakeContext.interrupt(); logger(DEBUGGING) << "Handshake with " << na << " timed out, interrupt it"; }); @@ -1049,7 +1050,9 @@ namespace CryptoNote net_connection_id excludeId = excludeConnection ? *excludeConnection : boost::value_initialized(); forEachConnection([&](P2pConnectionContext& conn) { - if (conn.peerId && conn.m_connection_id != excludeId) { + if (conn.peerId && conn.m_connection_id != excludeId && + (conn.m_state == CryptoNoteConnectionContext::state_normal || + conn.m_state == CryptoNoteConnectionContext::state_synchronizing)) { conn.pushMessage(P2pMessage(P2pMessage::NOTIFY, command, data_buff)); } }); @@ -1068,41 +1071,48 @@ namespace CryptoNote } //----------------------------------------------------------------------------------- - bool NodeServer::try_ping(basic_node_data& node_data, P2pConnectionContext& context) - { - if(!node_data.my_port) + bool NodeServer::try_ping(basic_node_data& node_data, P2pConnectionContext& context) { + if(!node_data.my_port) { return false; - - uint32_t actual_ip = context.m_remote_ip; - if(!m_peerlist.is_ip_allowed(actual_ip)) - return false; - - std::string ip = Common::ipAddressToString(actual_ip); - auto port = node_data.my_port; - PeerIdType pr = node_data.peer_id; - - try { - System::TcpConnector connector(m_dispatcher); - System::TcpConnection conn = connector.connect(System::Ipv4Address(ip), static_cast(port)); - - LevinProtocol proto(conn); - - COMMAND_PING::request req; - COMMAND_PING::response rsp; - proto.invoke(COMMAND_PING::ID, req, rsp); - - if (rsp.status != PING_OK_RESPONSE_STATUS_TEXT || pr != rsp.peer_id) { - logger(Logging::DEBUGGING) << context << "back ping invoke wrong response \"" << rsp.status << "\" from" << ip << ":" << port << ", hsh_peer_id=" << pr << ", rsp.peer_id=" << rsp.peer_id; - return false; - } - - return true; - - } catch (std::exception& e) { - logger(Logging::DEBUGGING) << context << "back ping to " << ip << ":" << port << " failed: " << e.what(); } - return false; + uint32_t actual_ip = context.m_remote_ip; + if(!m_peerlist.is_ip_allowed(actual_ip)) { + return false; + } + + auto ip = Common::ipAddressToString(actual_ip); + auto port = node_data.my_port; + auto peerId = node_data.peer_id; + + try { + COMMAND_PING::request req; + COMMAND_PING::response rsp; + System::Context<> pingContext(m_dispatcher, [&] { + System::TcpConnector connector(m_dispatcher); + auto connection = connector.connect(System::Ipv4Address(ip), static_cast(port)); + LevinProtocol(connection).invoke(COMMAND_PING::ID, req, rsp); + }); + + System::Context<> timeoutContext(m_dispatcher, [&] { + System::Timer(m_dispatcher).sleep(std::chrono::milliseconds(m_config.m_net_config.connection_timeout * 2)); + logger(DEBUGGING) << context << "Back ping timed out" << ip << ":" << port; + pingContext.interrupt(); + }); + + pingContext.get(); + + if (rsp.status != PING_OK_RESPONSE_STATUS_TEXT || peerId != rsp.peer_id) { + logger(DEBUGGING) << context << "Back ping invoke wrong response \"" << rsp.status << "\" from" << ip + << ":" << port << ", hsh_peer_id=" << peerId << ", rsp.peer_id=" << rsp.peer_id; + return false; + } + } catch (std::exception& e) { + logger(DEBUGGING) << context << "Back ping connection to " << ip << ":" << port << " failed: " << e.what(); + return false; + } + + return true; } //----------------------------------------------------------------------------------- @@ -1166,7 +1176,7 @@ namespace CryptoNote pe.id = peer_id_l; m_peerlist.append_with_peer_white(pe); - logger(Logging::TRACE) << context << "PING SUCCESS " << Common::ipAddressToString(context.m_remote_ip) << ":" << port_l; + logger(Logging::TRACE) << context << "BACK PING SUCCESS, " << Common::ipAddressToString(context.m_remote_ip) << ":" << port_l << " added to whitelist"; } } diff --git a/src/P2p/NetNodeConfig.cpp b/src/P2p/NetNodeConfig.cpp index 643b39a9..9dec3b36 100755 --- a/src/P2p/NetNodeConfig.cpp +++ b/src/P2p/NetNodeConfig.cpp @@ -74,8 +74,8 @@ void NetNodeConfig::initOptions(boost::program_options::options_description& des } NetNodeConfig::NetNodeConfig() { - bindIp = "0.0.0.0"; - bindPort = P2P_DEFAULT_PORT; + bindIp = ""; + bindPort = 0; externalPort = 0; allowLocalIp = false; hideMyPort = false; @@ -84,23 +84,37 @@ NetNodeConfig::NetNodeConfig() { bool NetNodeConfig::init(const boost::program_options::variables_map& vm) { - bindIp = command_line::get_arg(vm, arg_p2p_bind_ip); - bindPort = command_line::get_arg(vm, arg_p2p_bind_port); - externalPort = command_line::get_arg(vm, arg_p2p_external_port); - allowLocalIp = command_line::get_arg(vm, arg_p2p_allow_local_ip); - configFolder = command_line::get_arg(vm, command_line::arg_data_dir); + if (vm.count(arg_p2p_bind_ip.name) != 0 && (!vm[arg_p2p_bind_ip.name].defaulted() || bindIp.empty())) { + bindIp = command_line::get_arg(vm, arg_p2p_bind_ip); + } + + if (vm.count(arg_p2p_bind_port.name) != 0 && (!vm[arg_p2p_bind_port.name].defaulted() || bindPort == 0)) { + bindPort = command_line::get_arg(vm, arg_p2p_bind_port); + } + + if (vm.count(arg_p2p_external_port.name) != 0 && (!vm[arg_p2p_external_port.name].defaulted() || externalPort == 0)) { + externalPort = command_line::get_arg(vm, arg_p2p_external_port); + } + + if (vm.count(arg_p2p_allow_local_ip.name) != 0 && (!vm[arg_p2p_allow_local_ip.name].defaulted() || !allowLocalIp)) { + allowLocalIp = command_line::get_arg(vm, arg_p2p_allow_local_ip); + } + + if (vm.count(command_line::arg_data_dir.name) != 0 && (!vm[command_line::arg_data_dir.name].defaulted() || configFolder == Tools::getDefaultDataDirectory())) { + configFolder = command_line::get_arg(vm, command_line::arg_data_dir); + } + p2pStateFilename = CryptoNote::parameters::P2P_NET_DATA_FILENAME; - if (command_line::has_arg(vm, arg_p2p_add_peer)) - { + if (command_line::has_arg(vm, arg_p2p_add_peer)) { std::vector perrs = command_line::get_arg(vm, arg_p2p_add_peer); - for(const std::string& pr_str: perrs) - { + for(const std::string& pr_str: perrs) { PeerlistEntry pe = boost::value_initialized(); pe.id = Crypto::rand(); if (!parsePeerFromString(pe.adr, pr_str)) { return false; } + peers.push_back(pe); } } @@ -120,8 +134,9 @@ bool NetNodeConfig::init(const boost::program_options::variables_map& vm) return false; } - if(command_line::has_arg(vm, arg_p2p_hide_my_port)) + if (command_line::has_arg(vm, arg_p2p_hide_my_port)) { hideMyPort = true; + } return true; } diff --git a/src/PaymentGateService/PaymentServiceConfiguration.cpp b/src/PaymentGateService/PaymentServiceConfiguration.cpp index d9401fc8..7721ae57 100644 --- a/src/PaymentGateService/PaymentServiceConfiguration.cpp +++ b/src/PaymentGateService/PaymentServiceConfiguration.cpp @@ -36,6 +36,8 @@ Configuration::Configuration() { testnet = false; printAddresses = false; logLevel = Logging::INFO; + bindAddress = ""; + bindPort = 0; } void Configuration::initOptions(boost::program_options::options_description& desc) { @@ -57,15 +59,15 @@ void Configuration::initOptions(boost::program_options::options_description& des } void Configuration::init(const boost::program_options::variables_map& options) { - if (options.count("daemon")) { + if (options.count("daemon") != 0) { daemonize = true; } - if (options.count("register-service")) { + if (options.count("register-service") != 0) { registerService = true; } - if (options.count("unregister-service")) { + if (options.count("unregister-service") != 0) { unregisterService = true; } @@ -73,15 +75,15 @@ void Configuration::init(const boost::program_options::variables_map& options) { throw ConfigurationError("It's impossible to use both \"register-service\" and \"unregister-service\" at the same time"); } - if (options.count("testnet")) { + if (options.count("testnet") != 0) { testnet = true; } - if (options.count("log-file")) { + if (options.count("log-file") != 0) { logFile = options["log-file"].as(); } - if (options.count("log-level")) { + if (options.count("log-level") != 0) { logLevel = options["log-level"].as(); if (logLevel > Logging::TRACE) { std::string error = "log-level option must be in " + std::to_string(Logging::FATAL) + ".." + std::to_string(Logging::TRACE) + " interval"; @@ -89,31 +91,31 @@ void Configuration::init(const boost::program_options::variables_map& options) { } } - if (options.count("server-root")) { + if (options.count("server-root") != 0) { serverRoot = options["server-root"].as(); } - if (options.count("bind-address")) { + if (options.count("bind-address") != 0 && (!options["bind-address"].defaulted() || bindAddress.empty())) { bindAddress = options["bind-address"].as(); } - if (options.count("bind-port")) { + if (options.count("bind-port") != 0 && (!options["bind-port"].defaulted() || bindPort == 0)) { bindPort = options["bind-port"].as(); } - if (options.count("wallet-file")) { + if (options.count("wallet-file") != 0) { walletFile = options["wallet-file"].as(); } - if (options.count("wallet-password")) { + if (options.count("wallet-password") != 0) { walletPassword = options["wallet-password"].as(); } - if (options.count("generate-wallet")) { + if (options.count("generate-wallet") != 0) { generateNewWallet = true; } - if (options.count("address")) { + if (options.count("address") != 0) { printAddresses = true; } diff --git a/src/PaymentGateService/RpcNodeConfiguration.cpp b/src/PaymentGateService/RpcNodeConfiguration.cpp index a0a7a04d..2152e407 100644 --- a/src/PaymentGateService/RpcNodeConfiguration.cpp +++ b/src/PaymentGateService/RpcNodeConfiguration.cpp @@ -22,8 +22,8 @@ namespace PaymentService { namespace po = boost::program_options; RpcNodeConfiguration::RpcNodeConfiguration() { - daemonHost = "127.0.0.1"; - daemonPort = 8081; + daemonHost = ""; + daemonPort = 0; } void RpcNodeConfiguration::initOptions(boost::program_options::options_description& desc) { @@ -33,11 +33,11 @@ void RpcNodeConfiguration::initOptions(boost::program_options::options_descripti } void RpcNodeConfiguration::init(const boost::program_options::variables_map& options) { - if (options.count("daemon-address")) { + if (options.count("daemon-address") != 0 && (!options["daemon-address"].defaulted() || daemonHost.empty())) { daemonHost = options["daemon-address"].as(); } - if (options.count("daemon-port")) { + if (options.count("daemon-port") != 0 && (!options["daemon-port"].defaulted() || daemonPort == 0)) { daemonPort = options["daemon-port"].as(); } } diff --git a/src/Platform/Linux/System/Future.h b/src/Platform/Linux/System/Future.h new file mode 100644 index 00000000..65809d9e --- /dev/null +++ b/src/Platform/Linux/System/Future.h @@ -0,0 +1,34 @@ +// 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 System { + +namespace Detail { + +template using Future = std::future; + +template Future async(std::function&& operation) { + return std::async(std::launch::async, std::move(operation)); +} + +} + +} diff --git a/src/Platform/OSX/System/Future.h b/src/Platform/OSX/System/Future.h new file mode 100644 index 00000000..5e4649cd --- /dev/null +++ b/src/Platform/OSX/System/Future.h @@ -0,0 +1,170 @@ +// 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 + +namespace System { + +namespace Detail { + +namespace { + +enum class State : unsigned { + STARTED, + COMPLETED, + CONSUMED +}; + +} + +// Simplest possible future implementation. The reason why this class even exist is because currenty std future has a +// memory corrupting bug on OSX. Spawn a new thread, execute procedure in it, get result, and wait thread to shut down. +// Actualy, this is what libstdc++'s std::future is doing. +template class Future { +public: + // Create a new thread, and run `operation` in it. + explicit Future(std::function&& operation) : procedure(std::move(operation)), state(State::STARTED), worker{[this] { asyncOp(); }} { + } + + // Wait for async op to complete, then if thread wasn't detached, join it. + ~Future() { + wait(); + if (worker.joinable()) { + worker.join(); + } + } + + // Get result of async operation. UB if called more than once. + T get() const { + assert(state != State::CONSUMED); + wait(); + state = State::CONSUMED; + if (currentException != nullptr) { + std::rethrow_exception(currentException); + } + + return std::move(result); + } + + // Wait for async operation to complete, if op is already completed, return immediately. + void wait() const { + std::unique_lock guard(operationMutex); + while (state == State::STARTED) { + operationCondition.wait(guard); + } + } + + bool valid() const { + std::unique_lock guard(operationMutex); + return state != State::CONSUMED; + } + +private: + // This function is executed in a separate thread. + void asyncOp() { + try { + assert(procedure != nullptr); + result = procedure(); + } catch (...) { + currentException = std::current_exception(); + } + + std::unique_lock guard(operationMutex); + state = State::COMPLETED; + operationCondition.notify_one(); + } + + mutable T result; + std::function procedure; + std::exception_ptr currentException; + mutable std::mutex operationMutex; + mutable std::condition_variable operationCondition; + mutable State state; + std::thread worker; +}; + +template<> class Future { +public: + // Create a new thread, and run `operation` in it. + explicit Future(std::function&& operation) : procedure(std::move(operation)), state(State::STARTED), worker{[this] { asyncOp(); }} { + } + + // Wait for async op to complete, then if thread wasn't detached, join it. + ~Future() { + wait(); + if (worker.joinable()) { + worker.join(); + } + } + + // Get result of async operation. UB if called more than once. + void get() const { + assert(state != State::CONSUMED); + wait(); + state = State::CONSUMED; + if (currentException != nullptr) { + std::rethrow_exception(currentException); + } + } + + // Wait for async operation to complete, if op is already completed, return immediately. + void wait() const { + std::unique_lock guard(operationMutex); + while (state == State::STARTED) { + operationCondition.wait(guard); + } + } + + bool valid() const { + std::unique_lock guard(operationMutex); + return state != State::CONSUMED; + } + +private: + // This function is executed in a separate thread. + void asyncOp() { + try { + assert(procedure != nullptr); + procedure(); + } catch (...) { + currentException = std::current_exception(); + } + + std::unique_lock guard(operationMutex); + state = State::COMPLETED; + operationCondition.notify_one(); + } + + std::function procedure; + std::exception_ptr currentException; + mutable std::mutex operationMutex; + mutable std::condition_variable operationCondition; + mutable State state; + std::thread worker; +}; + +template std::function async(std::function&& operation) { + return operation; +} + +} + +} diff --git a/src/Platform/Windows/System/Future.h b/src/Platform/Windows/System/Future.h new file mode 100644 index 00000000..3abe92c7 --- /dev/null +++ b/src/Platform/Windows/System/Future.h @@ -0,0 +1,35 @@ +// 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 System { + +namespace Detail { + +template using Future = std::future; + +template Future async(std::function&& operation) { + return std::async(std::launch::async, std::move(operation)); +} + +} + +} + diff --git a/src/Rpc/HttpClient.cpp b/src/Rpc/HttpClient.cpp index 83ddf90e..ef943530 100644 --- a/src/Rpc/HttpClient.cpp +++ b/src/Rpc/HttpClient.cpp @@ -52,10 +52,18 @@ void HttpClient::request(const HttpRequest &req, HttpResponse &res) { } void HttpClient::connect() { - auto ipAddr = System::Ipv4Resolver(m_dispatcher).resolve(m_address); - m_connection = System::TcpConnector(m_dispatcher).connect(ipAddr, m_port); - m_streamBuf.reset(new System::TcpStreambuf(m_connection)); - m_connected = true; + try { + auto ipAddr = System::Ipv4Resolver(m_dispatcher).resolve(m_address); + m_connection = System::TcpConnector(m_dispatcher).connect(ipAddr, m_port); + m_streamBuf.reset(new System::TcpStreambuf(m_connection)); + m_connected = true; + } catch (const std::exception& e) { + throw ConnectException(e.what()); + } +} + +bool HttpClient::isConnected() const { + return m_connected; } void HttpClient::disconnect() { @@ -75,4 +83,7 @@ void HttpClient::disconnect() { m_connected = false; } +ConnectException::ConnectException(const std::string& whatArg) : std::runtime_error(whatArg.c_str()) { +} + } diff --git a/src/Rpc/HttpClient.h b/src/Rpc/HttpClient.h index 7c1ee1ea..1fac3054 100755 --- a/src/Rpc/HttpClient.h +++ b/src/Rpc/HttpClient.h @@ -28,15 +28,21 @@ namespace CryptoNote { +class ConnectException : public std::runtime_error { +public: + ConnectException(const std::string& whatArg); +}; + class HttpClient { public: HttpClient(System::Dispatcher& dispatcher, const std::string& address, uint16_t port); ~HttpClient(); void request(const HttpRequest& req, HttpResponse& res); + + bool isConnected() const; private: - void connect(); void disconnect(); diff --git a/src/SimpleWallet/SimpleWallet.cpp b/src/SimpleWallet/SimpleWallet.cpp index 1f87036a..8217c46e 100644 --- a/src/SimpleWallet/SimpleWallet.cpp +++ b/src/SimpleWallet/SimpleWallet.cpp @@ -420,10 +420,24 @@ void printListTransfersItem(LoggerRef& logger, const WalletLegacyTransaction& tx logger(INFO, rowColor) << " "; //just to make logger print one endline } +std::string prepareWalletAddressFilename(const std::string& walletBaseName) { + return walletBaseName + ".address"; } -std::string simple_wallet::get_commands_str() -{ +bool writeAddressFile(const std::string& addressFilename, const std::string& address) { + std::ofstream addressFile(addressFilename, std::ios::out | std::ios::trunc | std::ios::binary); + if (!addressFile.good()) { + return false; + } + + addressFile << address; + + return true; +} + +} + +std::string simple_wallet::get_commands_str() { std::stringstream ss; ss << "Commands: " << ENDL; std::string usage = m_consoleHandler.getUsage(); @@ -433,8 +447,7 @@ std::string simple_wallet::get_commands_str() return ss.str(); } -bool simple_wallet::help(const std::vector &args/* = std::vector()*/) -{ +bool simple_wallet::help(const std::vector &args/* = std::vector()*/) { success_msg_writer() << get_commands_str(); return true; } @@ -451,8 +464,7 @@ simple_wallet::simple_wallet(System::Dispatcher& dispatcher, const CryptoNote::C logManager(log), logger(log, "simplewallet"), m_refresh_progress_reporter(*this), - m_initResultPromise(nullptr) -{ + m_initResultPromise(nullptr) { m_consoleHandler.setHandler("start_mining", boost::bind(&simple_wallet::start_mining, this, _1), "start_mining [] - Start mining in daemon"); m_consoleHandler.setHandler("stop_mining", boost::bind(&simple_wallet::stop_mining, this, _1), "Stop mining in daemon"); //m_consoleHandler.setHandler("refresh", boost::bind(&simple_wallet::refresh, this, _1), "Resynchronize transactions and balance"); @@ -473,22 +485,19 @@ simple_wallet::simple_wallet(System::Dispatcher& dispatcher, const CryptoNote::C m_consoleHandler.setHandler("exit", boost::bind(&simple_wallet::exit, this, _1), "Close wallet"); } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::set_log(const std::vector &args) -{ - if (args.size() != 1) - { +bool simple_wallet::set_log(const std::vector &args) { + if (args.size() != 1) { fail_msg_writer() << "use: set_log "; return true; } + uint16_t l = 0; - if (!Common::fromString(args[0], l)) - { + if (!Common::fromString(args[0], l)) { fail_msg_writer() << "wrong number format, use: set_log "; return true; } - if (l > Logging::TRACE) - { + if (l > Logging::TRACE) { fail_msg_writer() << "wrong number range, use: set_log "; return true; } @@ -497,8 +506,7 @@ bool simple_wallet::set_log(const std::vector &args) return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::init(const boost::program_options::variables_map& vm) -{ +bool simple_wallet::init(const boost::program_options::variables_map& vm) { handle_command_line(vm); if (!m_daemon_address.empty() && (!m_daemon_host.empty() || 0 != m_daemon_port)) { @@ -582,6 +590,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) std::promise errorPromise; std::future f_error = errorPromise.get_future(); auto callback = [&errorPromise](std::error_code e) {errorPromise.set_value(e); }; + + m_node->addObserver(static_cast(this)); m_node->init(callback); auto error = f_error.get(); if (error) { @@ -589,13 +599,23 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } - if (!m_generate_new.empty()) - { - bool r = new_wallet(walletFileName, pwd_container.password()); - if (!(r)) { logger(ERROR, BRIGHT_RED) << "account creation failed"; return false; } - } - else - { + if (!m_generate_new.empty()) { + std::string walletAddressFile = prepareWalletAddressFilename(m_generate_new); + boost::system::error_code ignore; + if (boost::filesystem::exists(walletAddressFile, ignore)) { + logger(ERROR, BRIGHT_RED) << "Address file already exists: " + walletAddressFile; + return false; + } + + if (!new_wallet(walletFileName, pwd_container.password())) { + logger(ERROR, BRIGHT_RED) << "account creation failed"; + return false; + } + + if (!writeAddressFile(walletAddressFile, m_wallet->getAddress())) { + logger(WARNING, BRIGHT_RED) << "Couldn't write wallet address file: " + walletAddressFile; + } + } else { m_wallet.reset(new WalletLegacy(m_currency, *m_node)); try { @@ -606,7 +626,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) } m_wallet->addObserver(this); - m_node->addObserver(this); + m_node->addObserver(static_cast(this)); logger(INFO, BRIGHT_WHITE) << "Opened wallet: " << m_wallet->getAddress(); @@ -619,16 +639,18 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::deinit() -{ +bool simple_wallet::deinit() { + m_wallet->removeObserver(this); + m_node->removeObserver(static_cast(this)); + m_node->removeObserver(static_cast(this)); + if (!m_wallet.get()) return true; return close_wallet(); } //---------------------------------------------------------------------------------------------------- -void simple_wallet::handle_command_line(const boost::program_options::variables_map& vm) -{ +void simple_wallet::handle_command_line(const boost::program_options::variables_map& vm) { m_wallet_file_arg = command_line::get_arg(vm, arg_wallet_file); m_generate_new = command_line::get_arg(vm, arg_generate_new_wallet); m_daemon_address = command_line::get_arg(vm, arg_daemon_address); @@ -636,15 +658,13 @@ void simple_wallet::handle_command_line(const boost::program_options::variables_ m_daemon_port = command_line::get_arg(vm, arg_daemon_port); } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string& password) -{ +bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string& password) { m_wallet_file = wallet_file; m_wallet.reset(new WalletLegacy(m_currency, *m_node.get())); - m_node->addObserver(this); + m_node->addObserver(static_cast(this)); m_wallet->addObserver(this); - try - { + try { m_initResultPromise.reset(new std::promise()); std::future f_initError = m_initResultPromise->get_future(); m_wallet->initAndGenerate(password); @@ -669,8 +689,7 @@ bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string "Generated new wallet: " << m_wallet->getAddress() << std::endl << "view key: " << Common::podToHex(keys.viewSecretKey); } - catch (const std::exception& e) - { + catch (const std::exception& e) { fail_msg_writer() << "failed to generate new wallet: " << e.what(); return false; } @@ -690,9 +709,7 @@ bool simple_wallet::close_wallet() { try { CryptoNote::WalletHelper::storeWallet(*m_wallet, m_wallet_file); - } - catch (const std::exception& e) - { + } catch (const std::exception& e) { fail_msg_writer() << e.what(); return false; } @@ -706,13 +723,10 @@ bool simple_wallet::close_wallet() //---------------------------------------------------------------------------------------------------- bool simple_wallet::save(const std::vector &args) { - try - { + try { CryptoNote::WalletHelper::storeWallet(*m_wallet, m_wallet_file); success_msg_writer() << "Wallet data saved"; - } - catch (const std::exception& e) - { + } catch (const std::exception& e) { fail_msg_writer() << e.what(); } @@ -725,31 +739,24 @@ bool simple_wallet::reset(const std::vector &args) { return true; } -bool simple_wallet::start_mining(const std::vector& args) -{ +bool simple_wallet::start_mining(const std::vector& args) { COMMAND_RPC_START_MINING::request req; req.miner_address = m_wallet->getAddress(); bool ok = true; size_t max_mining_threads_count = (std::max)(std::thread::hardware_concurrency(), static_cast(2)); - if (0 == args.size()) - { + if (0 == args.size()) { req.threads_count = 1; - } - else if (1 == args.size()) - { + } else if (1 == args.size()) { uint16_t num = 1; ok = Common::fromString(args[0], num); ok = ok && (1 <= num && num <= max_mining_threads_count); req.threads_count = num; - } - else - { + } else { ok = false; } - if (!ok) - { + if (!ok) { fail_msg_writer() << "invalid arguments. Please use start_mining [], " << " should be from 1 to " << max_mining_threads_count; return true; @@ -769,6 +776,8 @@ bool simple_wallet::start_mining(const std::vector& args) else fail_msg_writer() << "mining has NOT been started: " << err; + } catch (const ConnectException&) { + printConnectionError(); } catch (const std::exception& e) { fail_msg_writer() << "Failed to invoke rpc method: " << e.what(); } @@ -783,12 +792,15 @@ bool simple_wallet::stop_mining(const std::vector& args) try { HttpClient httpClient(m_dispatcher, m_daemon_host, m_daemon_port); + invokeJsonCommand(httpClient, "/stop_mining", req, res); std::string err = interpret_rpc_response(true, res.status); if (err.empty()) success_msg_writer() << "Mining stopped in daemon"; else fail_msg_writer() << "mining has NOT been stopped: " << err; + } catch (const ConnectException&) { + printConnectionError(); } catch (const std::exception& e) { fail_msg_writer() << "Failed to invoke rpc method: " << e.what(); } @@ -802,13 +814,19 @@ void simple_wallet::initCompleted(std::error_code result) { } } //---------------------------------------------------------------------------------------------------- -void simple_wallet::localBlockchainUpdated(uint32_t height) -{ +void simple_wallet::localBlockchainUpdated(uint32_t height) { m_refresh_progress_reporter.update(height, false); } //---------------------------------------------------------------------------------------------------- -void simple_wallet::externalTransactionCreated(CryptoNote::TransactionId transactionId) -{ +void simple_wallet::connectionStatusUpdated(bool connected) { + if (connected) { + logger(INFO, GREEN) << "Wallet connected to daemon."; + } else { + printConnectionError(); + } +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::externalTransactionCreated(CryptoNote::TransactionId transactionId) { WalletLegacyTransaction txInfo; m_wallet->getTransaction(transactionId, txInfo); @@ -836,16 +854,14 @@ void simple_wallet::externalTransactionCreated(CryptoNote::TransactionId transac } } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::show_balance(const std::vector& args/* = std::vector()*/) -{ +bool simple_wallet::show_balance(const std::vector& args/* = std::vector()*/) { success_msg_writer() << "available balance: " << m_currency.formatAmount(m_wallet->actualBalance()) << ", locked amount: " << m_currency.formatAmount(m_wallet->pendingBalance()); return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::show_incoming_transfers(const std::vector& args) -{ +bool simple_wallet::show_incoming_transfers(const std::vector& args) { bool hasTransfers = false; size_t transactionsCount = m_wallet->getTransactionCount(); for (size_t trantransactionNumber = 0; trantransactionNumber < transactionsCount; ++trantransactionNumber) { @@ -888,10 +904,8 @@ bool simple_wallet::listTransfers(const std::vector& args) { return true; } -bool simple_wallet::show_payments(const std::vector &args) -{ - if (args.empty()) - { +bool simple_wallet::show_payments(const std::vector &args) { + if (args.empty()) { fail_msg_writer() << "expected at least one payment ID"; return true; } @@ -901,11 +915,9 @@ bool simple_wallet::show_payments(const std::vector &args) " height\t amount "; bool payments_found = false; - for (const std::string& arg: args) - { + for (const std::string& arg: args) { Crypto::Hash expectedPaymentId; - if (CryptoNote::parsePaymentId(arg, expectedPaymentId)) - { + if (CryptoNote::parsePaymentId(arg, expectedPaymentId)) { size_t transactionsCount = m_wallet->getTransactionCount(); for (size_t trantransactionNumber = 0; trantransactionNumber < transactionsCount; ++trantransactionNumber) { WalletLegacyTransaction txInfo; @@ -938,8 +950,7 @@ bool simple_wallet::show_payments(const std::vector &args) return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::show_blockchain_height(const std::vector& args) -{ +bool simple_wallet::show_blockchain_height(const std::vector& args) { try { uint64_t bc_height = m_node->getLastLocalBlockHeight(); success_msg_writer() << bc_height; @@ -950,10 +961,8 @@ bool simple_wallet::show_blockchain_height(const std::vector& args) return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::transfer(const std::vector &args) -{ - try - { +bool simple_wallet::transfer(const std::vector &args) { + try { TransferCommand cmd(m_currency); if (!cmd.parseArguments(logger, args)) @@ -989,17 +998,11 @@ bool simple_wallet::transfer(const std::vector &args) fail_msg_writer() << e.what(); return true; } - } - catch (const std::system_error& e) - { - fail_msg_writer() << "unexpected error: " << e.what(); - } - catch (const std::exception& e) - { - fail_msg_writer() << "unexpected error: " << e.what(); - } - catch (...) - { + } catch (const std::system_error& e) { + fail_msg_writer() << e.what(); + } catch (const std::exception& e) { + fail_msg_writer() << e.what(); + } catch (...) { fail_msg_writer() << "unknown error"; } @@ -1025,9 +1028,12 @@ bool simple_wallet::process_command(const std::vector &args) { return m_consoleHandler.runCommand(args); } +void simple_wallet::printConnectionError() const { + fail_msg_writer() << "wallet failed to connect to daemon (" << m_daemon_address << ")."; +} -int main(int argc, char* argv[]) -{ + +int main(int argc, char* argv[]) { #ifdef WIN32 _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); #endif @@ -1101,18 +1107,17 @@ int main(int argc, char* argv[]) if (command_line::has_arg(vm, Tools::wallet_rpc_server::arg_rpc_bind_port)) { //runs wallet with rpc interface - if (!command_line::has_arg(vm, arg_wallet_file)) - { + if (!command_line::has_arg(vm, arg_wallet_file)) { logger(ERROR, BRIGHT_RED) << "Wallet file not set."; return 1; } - if (!command_line::has_arg(vm, arg_daemon_address)) - { + + if (!command_line::has_arg(vm, arg_daemon_address)) { logger(ERROR, BRIGHT_RED) << "Daemon address not set."; return 1; } - if (!command_line::has_arg(vm, arg_password)) - { + + if (!command_line::has_arg(vm, arg_password)) { logger(ERROR, BRIGHT_RED) << "Wallet password not set."; return 1; } @@ -1179,9 +1184,7 @@ int main(int argc, char* argv[]) logger(INFO) << "Storing wallet..."; CryptoNote::WalletHelper::storeWallet(*wallet, walletFileName); logger(INFO, BRIGHT_GREEN) << "Stored ok"; - } - catch (const std::exception& e) - { + } catch (const std::exception& e) { logger(ERROR, BRIGHT_RED) << "Failed to store wallet: " << e.what(); return 1; } diff --git a/src/SimpleWallet/SimpleWallet.h b/src/SimpleWallet/SimpleWallet.h index 75ff383d..6bbe0541 100755 --- a/src/SimpleWallet/SimpleWallet.h +++ b/src/SimpleWallet/SimpleWallet.h @@ -23,12 +23,12 @@ #include #include "IWalletLegacy.h" -#include "INode.h" #include "PasswordContainer.h" #include "Common/ConsoleHandler.h" #include "CryptoNoteCore/CryptoNoteBasicImpl.h" #include "CryptoNoteCore/Currency.h" +#include "NodeRpcProxy/NodeRpcProxy.h" #include "WalletLegacy/WalletHelper.h" #include @@ -42,8 +42,7 @@ namespace CryptoNote /************************************************************************/ /* */ /************************************************************************/ - class simple_wallet : public CryptoNote::INodeObserver, public CryptoNote::IWalletLegacyObserver - { + class simple_wallet : public CryptoNote::INodeObserver, public CryptoNote::IWalletLegacyObserver, public CryptoNote::INodeRpcProxyObserver { public: simple_wallet(System::Dispatcher& dispatcher, const CryptoNote::Currency& currency, Logging::LoggerManager& log); @@ -63,7 +62,7 @@ namespace CryptoNote return logger(Logging::INFO, color ? Logging::GREEN : Logging::DEFAULT); } - Logging::LoggerMessage fail_msg_writer() { + Logging::LoggerMessage fail_msg_writer() const { auto msg = logger(Logging::ERROR, Logging::BRIGHT_RED); msg << "Error: "; return msg; @@ -94,6 +93,8 @@ namespace CryptoNote bool ask_wallet_create_if_needed(); + void printConnectionError() const; + //---------------- IWalletObserver ------------------------- virtual void initCompleted(std::error_code result) override; virtual void externalTransactionCreated(CryptoNote::TransactionId transactionId) override; @@ -103,6 +104,10 @@ namespace CryptoNote virtual void localBlockchainUpdated(uint32_t height) override; //---------------------------------------------------------- + //----------------- INodeRpcProxyObserver -------------------------- + virtual void connectionStatusUpdated(bool connected) override; + //---------------------------------------------------------- + friend class refresh_progress_reporter_t; class refresh_progress_reporter_t @@ -174,7 +179,7 @@ namespace CryptoNote System::Dispatcher& m_dispatcher; Logging::LoggerRef logger; - std::unique_ptr m_node; + std::unique_ptr m_node; std::unique_ptr m_wallet; refresh_progress_reporter_t m_refresh_progress_reporter; }; diff --git a/src/System/RemoteContext.h b/src/System/RemoteContext.h new file mode 100644 index 00000000..39711b28 --- /dev/null +++ b/src/System/RemoteContext.h @@ -0,0 +1,102 @@ +// 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 + +namespace System { + +template class RemoteContext { +public: + // Start a thread, execute operation in it, continue execution of current context. + RemoteContext(Dispatcher& d, std::function&& operation) + : dispatcher(d), event(d), procedure(std::move(operation)), future(System::Detail::async([this] { return asyncProcedure(); })), interrupted(false) { + } + + // Run other task on dispatcher until future is ready, then return lambda's result, or rethrow exception. UB if called more than once. + T get() const { + wait(); + return future.get(); + } + + // Run other task on dispatcher until future is ready. + void wait() const { + while (!event.get()) { + try { + event.wait(); + } catch (InterruptedException&) { + interrupted = true; + } + } + + if (interrupted) { + dispatcher.interrupt(); + } + } + + // Wait future to complete. + ~RemoteContext() { + try { + wait(); + } catch (std::exception&) { + } + + try { + // windows future implementation doesn't wait for completion on destruction + if (future.valid()) { + future.wait(); + } + } catch (std::exception&) { + } + } + +private: + struct NotifyOnDestruction { + NotifyOnDestruction(Dispatcher& d, Event& e) : dispatcher(d), event(e) { + } + + ~NotifyOnDestruction() { + // make a local copy; event reference will be dead when function is called + auto localEvent = &event; + // die if this throws... + dispatcher.remoteSpawn([=] { localEvent->set(); }); + } + + Dispatcher& dispatcher; + Event& event; + }; + + // This function is executed in future object + T asyncProcedure() { + NotifyOnDestruction guard(dispatcher, event); + assert(procedure != nullptr); + return procedure(); + } + + Dispatcher& dispatcher; + mutable Event event; + std::function procedure; + mutable System::Detail::Future future; + mutable bool interrupted; +}; + +} diff --git a/src/Wallet/IFusionManager.h b/src/Wallet/IFusionManager.h new file mode 100644 index 00000000..dd132900 --- /dev/null +++ b/src/Wallet/IFusionManager.h @@ -0,0 +1,39 @@ +// 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 { + +class IFusionManager { +public: + struct EstimateResult { + size_t belowThresholdCount; + size_t totalOutputCount; + }; + + virtual ~IFusionManager() {} + + virtual size_t createFusionTransaction(uint64_t threshold, uint64_t mixin) = 0; + virtual bool isFusionTransaction(size_t transactionId) const = 0; + virtual EstimateResult estimate(uint64_t threshold) const = 0; +}; + +} diff --git a/src/Wallet/WalletErrors.h b/src/Wallet/WalletErrors.h index 1a39710d..2943463e 100644 --- a/src/Wallet/WalletErrors.h +++ b/src/Wallet/WalletErrors.h @@ -41,7 +41,8 @@ enum WalletErrorCodes { OPERATION_CANCELLED, TX_TRANSFER_IMPOSSIBLE, WRONG_VERSION, - FEE_TOO_SMALL + FEE_TOO_SMALL, + KEY_GENERATION_ERROR }; // custom category: @@ -75,6 +76,7 @@ public: case TX_TRANSFER_IMPOSSIBLE: return "Transaction transfer impossible"; case WRONG_VERSION: return "Wrong version"; case FEE_TOO_SMALL: return "Transaction fee is too small"; + case KEY_GENERATION_ERROR: return "Cannot generate new key"; default: return "Unknown error"; } } diff --git a/src/Wallet/WalletGreen.cpp b/src/Wallet/WalletGreen.cpp index 57931cf5..f83c5a40 100755 --- a/src/Wallet/WalletGreen.cpp +++ b/src/Wallet/WalletGreen.cpp @@ -136,6 +136,22 @@ CryptoNote::WalletEvent makeMoneyUnlockedEvent() { return event; } +CryptoNote::WalletEvent makeSyncProgressUpdatedEvent(uint32_t current, uint32_t total) { + CryptoNote::WalletEvent event; + event.type = CryptoNote::WalletEventType::SYNC_PROGRESS_UPDATED; + event.synchronizationProgressUpdated.processedBlockCount = current; + event.synchronizationProgressUpdated.totalBlockCount = total; + + return event; +} + +CryptoNote::WalletEvent makeSyncCompletedEvent() { + CryptoNote::WalletEvent event; + event.type = CryptoNote::WalletEventType::SYNC_COMPLETED; + + return event; +} + } namespace CryptoNote { @@ -162,18 +178,20 @@ WalletGreen::~WalletGreen() { } void WalletGreen::initialize(const std::string& password) { - if (m_state != WalletState::NOT_INITIALIZED) { - throw std::system_error(make_error_code(CryptoNote::error::ALREADY_INITIALIZED)); + Crypto::PublicKey viewPublicKey; + Crypto::SecretKey viewSecretKey; + Crypto::generate_keys(viewPublicKey, viewSecretKey); + + initWithKeys(viewPublicKey, viewSecretKey, password); +} + +void WalletGreen::initializeWithViewKey(const Crypto::SecretKey& viewSecretKey, const std::string& password) { + Crypto::PublicKey viewPublicKey; + if (!Crypto::secret_key_to_public_key(viewSecretKey, viewPublicKey)) { + throw std::system_error(make_error_code(CryptoNote::error::KEY_GENERATION_ERROR)); } - throwIfStopped(); - - Crypto::generate_keys(m_viewPublicKey, m_viewSecretKey); - m_password = password; - - m_blockchainSynchronizer.addObserver(this); - - m_state = WalletState::INITIALIZED; + initWithKeys(viewPublicKey, viewSecretKey, password); } void WalletGreen::shutdown() { @@ -210,6 +228,22 @@ void WalletGreen::clearCaches() { m_pendingBalance = 0; } +void WalletGreen::initWithKeys(const Crypto::PublicKey& viewPublicKey, const Crypto::SecretKey& viewSecretKey, const std::string& password) { + if (m_state != WalletState::NOT_INITIALIZED) { + throw std::system_error(make_error_code(CryptoNote::error::ALREADY_INITIALIZED)); + } + + throwIfStopped(); + + m_viewPublicKey = viewPublicKey; + m_viewSecretKey = viewSecretKey; + m_password = password; + + m_blockchainSynchronizer.addObserver(this); + + m_state = WalletState::INITIALIZED; +} + void WalletGreen::save(std::ostream& destination, bool saveDetails, bool saveCache) { throwIfNotInitialized(); throwIfStopped(); @@ -318,14 +352,42 @@ std::string WalletGreen::getAddress(size_t index) const { return m_currency.accountAddressAsString({ wallet.spendPublicKey, m_viewPublicKey }); } -std::string WalletGreen::createAddress() { - KeyPair spendKey; +KeyPair WalletGreen::getAddressSpendKey(size_t index) const { + throwIfNotInitialized(); + throwIfStopped(); - Crypto::generate_keys(spendKey.publicKey, spendKey.secretKey); - return createAddress(spendKey); + if (index >= m_walletsContainer.get().size()) { + throw std::system_error(std::make_error_code(std::errc::invalid_argument)); + } + + const WalletRecord& wallet = m_walletsContainer.get()[index]; + return {wallet.spendPublicKey, wallet.spendSecretKey}; } -std::string WalletGreen::createAddress(const KeyPair& spendKey) { +KeyPair WalletGreen::getViewKey() const { + throwIfNotInitialized(); + throwIfStopped(); + + return {m_viewPublicKey, m_viewSecretKey}; +} + +std::string WalletGreen::createAddress() { + KeyPair spendKey; + Crypto::generate_keys(spendKey.publicKey, spendKey.secretKey); + + return doCreateAddress(spendKey.publicKey, spendKey.secretKey); +} + +std::string WalletGreen::createAddress(const Crypto::SecretKey& spendSecretKey) { + Crypto::PublicKey spendPublicKey; + if (!Crypto::secret_key_to_public_key(spendSecretKey, spendPublicKey) ) { + throw std::system_error(make_error_code(CryptoNote::error::KEY_GENERATION_ERROR)); + } + + return doCreateAddress(spendPublicKey, spendSecretKey); +} + +std::string WalletGreen::doCreateAddress(const Crypto::PublicKey& spendPublicKey, const Crypto::SecretKey& spendSecretKey) { throwIfNotInitialized(); throwIfStopped(); @@ -333,22 +395,22 @@ std::string WalletGreen::createAddress(const KeyPair& spendKey) { m_blockchainSynchronizer.stop(); } - addWallet(spendKey); - std::string address = m_currency.accountAddressAsString({ spendKey.publicKey, m_viewPublicKey }); + addWallet(spendPublicKey, spendSecretKey); + std::string address = m_currency.accountAddressAsString({ spendPublicKey, m_viewPublicKey }); m_blockchainSynchronizer.start(); return address; } -void WalletGreen::addWallet(const KeyPair& spendKey) { +void WalletGreen::addWallet(const Crypto::PublicKey& spendPublicKey, const Crypto::SecretKey& spendSecretKey) { time_t creationTimestamp = time(nullptr); AccountSubscription sub; sub.keys.address.viewPublicKey = m_viewPublicKey; - sub.keys.address.spendPublicKey = spendKey.publicKey; + sub.keys.address.spendPublicKey = spendPublicKey; sub.keys.viewSecretKey = m_viewSecretKey; - sub.keys.spendSecretKey = spendKey.secretKey; + sub.keys.spendSecretKey = spendSecretKey; sub.transactionSpendableAge = 10; sub.syncStart.height = 0; sub.syncStart.timestamp = static_cast(creationTimestamp) - (60 * 60 * 24); @@ -357,8 +419,8 @@ void WalletGreen::addWallet(const KeyPair& spendKey) { ITransfersContainer* container = &trSubscription.getContainer(); WalletRecord wallet; - wallet.spendPublicKey = spendKey.publicKey; - wallet.spendSecretKey = spendKey.secretKey; + wallet.spendPublicKey = spendPublicKey; + wallet.spendSecretKey = spendSecretKey; wallet.container = container; wallet.creationTimestamp = creationTimestamp; trSubscription.addObserver(this); @@ -967,19 +1029,34 @@ void WalletGreen::onError(ITransfersSubscription* object, uint32_t height, std:: } void WalletGreen::synchronizationProgressUpdated(uint32_t current, uint32_t total) { - m_dispatcher.remoteSpawn( [current, this] () { this->onSynchronizationProgressUpdated(current); } ); + m_dispatcher.remoteSpawn( [current, total, this] () { onSynchronizationProgressUpdated(current, total); } ); } -void WalletGreen::onSynchronizationProgressUpdated(uint32_t current) { +void WalletGreen::synchronizationCompleted(std::error_code result) { + m_dispatcher.remoteSpawn([this] () { onSynchronizationCompleted(); } ); +} + +void WalletGreen::onSynchronizationProgressUpdated(uint32_t current, uint32_t total) { System::EventLock lk(m_readyEvent); if (m_state == WalletState::NOT_INITIALIZED) { return; } + pushEvent(makeSyncProgressUpdatedEvent(current, total)); unlockBalances(current); } +void WalletGreen::onSynchronizationCompleted() { + System::EventLock lk(m_readyEvent); + + if (m_state == WalletState::NOT_INITIALIZED) { + return; + } + + pushEvent(makeSyncCompletedEvent()); +} + void WalletGreen::unlockBalances(uint32_t height) { auto& index = m_unlockTransactionsJob.get(); auto upper = index.upper_bound(height); @@ -1219,4 +1296,19 @@ void WalletGreen::throwIfStopped() const { } } +size_t WalletGreen::createFusionTransaction(uint64_t threshold, uint64_t mixin) { + // TODO NOT IMPLEMENTED + throw std::runtime_error("WalletGreen::createFusionTransaction not implemented."); +} + +bool WalletGreen::isFusionTransaction(size_t transactionId) const { + // TODO NOT IMPLEMENTED + throw std::runtime_error("WalletGreen::isFusionTransaction not implemented."); +} + +IFusionManager::EstimateResult WalletGreen::estimate(uint64_t threshold) const { + // TODO NOT IMPLEMENTED + throw std::runtime_error("WalletGreen::estimate not implemented."); +} + } //namespace CryptoNote diff --git a/src/Wallet/WalletGreen.h b/src/Wallet/WalletGreen.h index 00031d9a..5cb18778 100755 --- a/src/Wallet/WalletGreen.h +++ b/src/Wallet/WalletGreen.h @@ -21,6 +21,7 @@ #include +#include "IFusionManager.h" #include "WalletIndices.h" #include @@ -32,12 +33,14 @@ namespace CryptoNote { class WalletGreen : public IWallet, ITransfersObserver, - IBlockchainSynchronizerObserver { + IBlockchainSynchronizerObserver, + IFusionManager { public: WalletGreen(System::Dispatcher& dispatcher, const Currency& currency, INode& node); virtual ~WalletGreen(); 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; @@ -46,8 +49,10 @@ 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 getViewKey() const override; virtual std::string createAddress() override; - virtual std::string createAddress(const KeyPair& spendKey) override; + virtual std::string createAddress(const Crypto::SecretKey& spendSecretKey) override; virtual void deleteAddress(const std::string& address) override; virtual uint64_t getActualBalance() const override; @@ -69,11 +74,17 @@ public: virtual void stop() override; virtual WalletEvent getEvent() override; + virtual size_t createFusionTransaction(uint64_t threshold, uint64_t mixin) override; + virtual bool isFusionTransaction(size_t transactionId) const override; + virtual IFusionManager::EstimateResult estimate(uint64_t threshold) const override; + protected: void throwIfNotInitialized() const; void throwIfStopped() const; void doShutdown(); void clearCaches(); + void initWithKeys(const Crypto::PublicKey& viewPublicKey, const Crypto::SecretKey& viewSecretKey, const std::string& password); + std::string doCreateAddress(const Crypto::PublicKey& spendPublicKey, const Crypto::SecretKey& spendSecretKey); struct InputInfo { TransactionTypes::InputKeyInfo keyInfo; @@ -100,10 +111,12 @@ protected: virtual void onTransactionUpdated(ITransfersSubscription* object, const Crypto::Hash& transactionHash) override; virtual void onTransactionDeleted(ITransfersSubscription* object, const Crypto::Hash& transactionHash) override; virtual void synchronizationProgressUpdated(uint32_t current, uint32_t total) override; + virtual void synchronizationCompleted(std::error_code result) override; void transactionUpdated(ITransfersSubscription* object, const Crypto::Hash& transactionHash); void transactionDeleted(ITransfersSubscription* object, const Crypto::Hash& transactionHash); - void onSynchronizationProgressUpdated(uint32_t current); + void onSynchronizationProgressUpdated(uint32_t current, uint32_t total); + void onSynchronizationCompleted(); std::vector pickWalletsWithMoney(); WalletOuts pickWallet(const std::string& address); @@ -116,7 +129,7 @@ protected: const WalletRecord& getWalletRecord(CryptoNote::ITransfersContainer* container) const; CryptoNote::AccountPublicAddress parseAddress(const std::string& address) const; - void addWallet(const KeyPair& spendKey); + void addWallet(const Crypto::PublicKey& spendPublicKey, const Crypto::SecretKey& spendSecretKey); bool isOutputUsed(const TransactionOutputInformation& out) const; void markOutputsSpent(const Crypto::Hash& transactionHash, const std::vector& selectedTransfers); void deleteSpentOutputs(const Crypto::Hash& transactionHash); diff --git a/src/version.h.in b/src/version.h.in index 81443d33..6af740ac 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.7" -#define PROJECT_VERSION_BUILD_NO "564" +#define PROJECT_VERSION "1.0.7.1" +#define PROJECT_VERSION_BUILD_NO "571" #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO "(" BUILD_COMMIT_ID ")" diff --git a/tests/System/RemoteContextTests.cpp b/tests/System/RemoteContextTests.cpp new file mode 100644 index 00000000..cd091342 --- /dev/null +++ b/tests/System/RemoteContextTests.cpp @@ -0,0 +1,146 @@ +// 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 +#include +#include +#include +#include +#include +#include + +using namespace System; + +class RemoteContextTests : public testing::Test { +public: + Dispatcher dispatcher; +}; + + +TEST_F(RemoteContextTests, getReturnsResult) { + RemoteContext context(dispatcher, [&] { + return 2; + }); + + ASSERT_EQ(2, context.get()); +} + +TEST_F(RemoteContextTests, getRethrowsException) { + RemoteContext<> context(dispatcher, [&] { + throw std::string("Hi there!"); + }); + + ASSERT_THROW(context.get(), std::string); +} + +TEST_F(RemoteContextTests, destructorIgnoresException) { + ASSERT_NO_THROW(RemoteContext<>(dispatcher, [&] { + throw std::string("Hi there!"); + })); +} + +TEST_F(RemoteContextTests, canBeUsedWithoutObject) { + ASSERT_EQ(42, RemoteContext(dispatcher, [&] { return 42; }).get()); +} + +TEST_F(RemoteContextTests, interruptIsInterruptingWait) { + ContextGroup cg(dispatcher); + cg.spawn([&] { + RemoteContext<> context(dispatcher, [&] { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + }); + ASSERT_NO_THROW(context.wait()); + ASSERT_TRUE(dispatcher.interrupted()); + }); + + cg.interrupt(); + cg.wait(); +} + +TEST_F(RemoteContextTests, interruptIsInterruptingGet) { + ContextGroup cg(dispatcher); + cg.spawn([&] { + RemoteContext<> context(dispatcher, [&] { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + }); + ASSERT_NO_THROW(context.wait()); + ASSERT_TRUE(dispatcher.interrupted()); + }); + + cg.interrupt(); + cg.wait(); +} + +TEST_F(RemoteContextTests, destructorIgnoresInterrupt) { + ContextGroup cg(dispatcher); + cg.spawn([&] { + ASSERT_NO_THROW(RemoteContext<>(dispatcher, [&] { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + })); + }); + + cg.interrupt(); + cg.wait(); +} + +TEST_F(RemoteContextTests, canExecuteOtherContextsWhileWaiting) { + auto start = std::chrono::high_resolution_clock::now(); + ContextGroup cg(dispatcher); + cg.spawn([&] { + RemoteContext<> context(dispatcher, [&] { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + }); + }); + cg.spawn([&] { + System::Timer(dispatcher).sleep(std::chrono::milliseconds(5)); + auto end = std::chrono::high_resolution_clock::now(); + ASSERT_GE(std::chrono::duration_cast(end - start).count(), 5); + ASSERT_LE(std::chrono::duration_cast(end - start).count(), 9); + }); + + cg.wait(); +} + +TEST_F(RemoteContextTests, waitMethodWaitsForContexCompletion) { + auto start = std::chrono::high_resolution_clock::now(); + ContextGroup cg(dispatcher); + cg.spawn([&] { + RemoteContext<> context(dispatcher, [&] { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + }); + }); + + cg.wait(); + auto end = std::chrono::high_resolution_clock::now(); + ASSERT_GE(std::chrono::duration_cast(end - start).count(), 10); +} + +TEST_F(RemoteContextTests, waitMethodWaitsForContexCompletionOnInterrupt) { + auto start = std::chrono::high_resolution_clock::now(); + ContextGroup cg(dispatcher); + cg.spawn([&] { + RemoteContext<> context(dispatcher, [&] { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + }); + }); + + cg.interrupt(); + cg.wait(); + auto end = std::chrono::high_resolution_clock::now(); + ASSERT_GE(std::chrono::duration_cast(end - start).count(), 10); +} + diff --git a/tests/UnitTests/INodeStubs.h b/tests/UnitTests/INodeStubs.h index 33aef2af..bf360697 100644 --- a/tests/UnitTests/INodeStubs.h +++ b/tests/UnitTests/INodeStubs.h @@ -79,8 +79,11 @@ public: void setGetNewBlocksLimit(size_t maxBlocks) { m_getMaxBlocks = maxBlocks; } - virtual uint32_t getLastLocalBlockHeight() const { return static_cast(m_blockchainGenerator.getBlockchain().size() - 1); }; - virtual uint32_t getLastKnownBlockHeight() const { return static_cast(m_blockchainGenerator.getBlockchain().size() - 1); }; + virtual uint32_t getLastLocalBlockHeight() const { return static_cast(m_blockchainGenerator.getBlockchain().size() - 1); } + virtual uint32_t getLastKnownBlockHeight() const { return static_cast(m_blockchainGenerator.getBlockchain().size() - 1); } + + virtual uint32_t getLocalBlockCount() const override { return static_cast(m_blockchainGenerator.getBlockchain().size()); } + virtual uint32_t getKnownBlockCount() const override { return static_cast(m_blockchainGenerator.getBlockchain().size()); } virtual void getNewBlocks(std::vector&& knownBlockIds, std::vector& newBlocks, uint32_t& startHeight, const Callback& callback); diff --git a/tests/UnitTests/TestWallet.cpp b/tests/UnitTests/TestWallet.cpp index 02a038bb..88e914d3 100755 --- a/tests/UnitTests/TestWallet.cpp +++ b/tests/UnitTests/TestWallet.cpp @@ -34,6 +34,7 @@ #include "WalletLegacy/WalletLegacySerializer.h" #include #include +#include using namespace Crypto; using namespace Common; @@ -129,6 +130,8 @@ protected: template void waitForValue(CryptoNote::WalletGreen& wallet, T value, std::function&& f); + bool waitForWalletEvent(CryptoNote::WalletGreen& wallet, CryptoNote::WalletEventType eventType, std::chrono::nanoseconds timeout); + void waitActualBalanceUpdated(); void waitActualBalanceUpdated(uint64_t prev); void waitActualBalanceUpdated(CryptoNote::WalletGreen& wallet, uint64_t prev); @@ -223,6 +226,28 @@ void WalletApi::waitForValue(CryptoNote::WalletGreen& wallet, T value, std::func } } +bool WalletApi::waitForWalletEvent(CryptoNote::WalletGreen& wallet, CryptoNote::WalletEventType eventType, std::chrono::nanoseconds timeout) { + System::Context<> eventContext(dispatcher, [&wallet, eventType] () { + CryptoNote::WalletEvent event; + + do { + event = wallet.getEvent(); + } while(event.type != eventType); + }); + + System::Context<> timeoutContext(dispatcher, [timeout, &eventContext, this] { + System::Timer(dispatcher).sleep(timeout); + eventContext.interrupt(); + }); + + try { + eventContext.get(); + return true; + } catch (System::InterruptedException&) { + return false; + } +} + void WalletApi::waitActualBalanceUpdated() { waitActualBalanceUpdated(alice, alice.getActualBalance()); } @@ -1145,3 +1170,93 @@ TEST_F(WalletApi, transferSmallFeeTransactionThrows) { ASSERT_ANY_THROW(sendMoneyToRandomAddressFrom(alice.getAddress(0), SENT, currency.minimumFee() - 1)); } + +TEST_F(WalletApi, initializeWithKeysSucceded) { + CryptoNote::WalletGreen wallet(dispatcher, currency, node); + + CryptoNote::KeyPair viewKeys; + Crypto::generate_keys(viewKeys.publicKey, viewKeys.secretKey); + ASSERT_NO_THROW(wallet.initializeWithViewKey(viewKeys.secretKey, "pass")); + + wallet.shutdown(); +} + +TEST_F(WalletApi, initializeWithKeysThrowsIfAlreadInitialized) { + CryptoNote::KeyPair viewKeys; + Crypto::generate_keys(viewKeys.publicKey, viewKeys.secretKey); + + ASSERT_ANY_THROW(alice.initializeWithViewKey(viewKeys.secretKey, "pass")); +} + +TEST_F(WalletApi, initializeWithKeysThrowsIfStopped) { + CryptoNote::WalletGreen wallet(dispatcher, currency, node); + wallet.stop(); + + CryptoNote::KeyPair viewKeys; + Crypto::generate_keys(viewKeys.publicKey, viewKeys.secretKey); + ASSERT_ANY_THROW(wallet.initializeWithViewKey(viewKeys.secretKey, "pass")); +} + +TEST_F(WalletApi, getViewKeyReturnsProperKey) { + CryptoNote::WalletGreen wallet(dispatcher, currency, node); + + CryptoNote::KeyPair viewKeys; + Crypto::generate_keys(viewKeys.publicKey, viewKeys.secretKey); + wallet.initializeWithViewKey(viewKeys.secretKey, "pass"); + + CryptoNote::KeyPair retrievedKeys = wallet.getViewKey(); + ASSERT_EQ(viewKeys.publicKey, retrievedKeys.publicKey); + ASSERT_EQ(viewKeys.secretKey, retrievedKeys.secretKey); + + wallet.shutdown(); +} + +TEST_F(WalletApi, getViewKeyThrowsIfNotInitialized) { + CryptoNote::WalletGreen wallet(dispatcher, currency, node); + ASSERT_ANY_THROW(wallet.getViewKey()); +} + +TEST_F(WalletApi, getViewKeyThrowsIfStopped) { + alice.stop(); + + ASSERT_ANY_THROW(alice.getViewKey()); +} + +TEST_F(WalletApi, getAddressSpendKeyReturnsProperKey) { + CryptoNote::KeyPair spendKeys; + Crypto::generate_keys(spendKeys.publicKey, spendKeys.secretKey); + + alice.createAddress(spendKeys.secretKey); + + CryptoNote::KeyPair retrievedKeys = alice.getAddressSpendKey(1); + ASSERT_EQ(spendKeys.publicKey, retrievedKeys.publicKey); + ASSERT_EQ(spendKeys.secretKey, retrievedKeys.secretKey); +} + +TEST_F(WalletApi, getAddressSpendKeyThrowsForWrongAddressIndex) { + ASSERT_ANY_THROW(alice.getAddressSpendKey(1)); +} + +TEST_F(WalletApi, getAddressSpendKeyThrowsIfNotInitialized) { + CryptoNote::WalletGreen wallet(dispatcher, currency, node); + ASSERT_ANY_THROW(wallet.getAddressSpendKey(0)); +} + +TEST_F(WalletApi, getAddressSpendKeyThrowsIfStopped) { + alice.stop(); + ASSERT_ANY_THROW(alice.getAddressSpendKey(0)); +} + +TEST_F(WalletApi, walletGetsSyncCompletedEvent) { + generator.generateEmptyBlocks(1); + node.updateObservers(); + + ASSERT_TRUE(waitForWalletEvent(alice, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(5))); +} + +TEST_F(WalletApi, walletGetsSyncProgressUpdatedEvent) { + generator.generateEmptyBlocks(1); + node.updateObservers(); + + ASSERT_TRUE(waitForWalletEvent(alice, CryptoNote::SYNC_PROGRESS_UPDATED, std::chrono::seconds(5))); +}