Daemon synchronization and SimpleWallet improvements

This commit is contained in:
Antonio Juarez 2015-08-19 18:06:24 +01:00
parent deda499fc9
commit a4b74eaa11
29 changed files with 1090 additions and 222 deletions

View file

@ -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;

View file

@ -161,7 +161,8 @@ const CheckpointData CHECKPOINTS[] = {
{789000, "acef490bbccce3b7b7ae8554a414f55413fbf4ca1472c6359b126a4439bd9f01"},
{796000, "04e387a00d35db21d4d93d04040b31f22573972a7e61d72cc07d0ab69bcb9c44"},
{800000, "d7fa4eea02e5ce60b949136569c0ea7ac71ea46e0065311054072ac415560b86"},
{804000, "bcc8b3782499aae508c40d5587d1cc5d68281435ea9bfc6804a262047f7b934d"}
{804000, "bcc8b3782499aae508c40d5587d1cc5d68281435ea9bfc6804a262047f7b934d"},
{810500, "302b2349f221232820adc3dadafd8a61b035491e33af669c78a687949eb0a381"}
};
} // CryptoNote

View file

@ -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) {

View file

@ -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";
}
}

View file

@ -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<std::error_code()>&& 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);
}

View file

@ -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<CryptoNote::INodeObserver> m_observerManager;
Tools::ObserverManager<CryptoNote::INodeRpcProxyObserver> 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<uint64_t> m_lastLocalBlockTimestamp;
bool m_connected;
};
}

View file

@ -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:

View file

@ -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<uint8_t*>(&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<const uint8_t*>(&head), sizeof(head));
if (out.size() > 0) {
m_conn.write(reinterpret_cast<const uint8_t*>(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<char*>(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<uint8_t*>(pos + offset), size - offset);
size_t read = m_conn.read(ptr + offset, size - offset);
if (read == 0) {
return false;
}

View file

@ -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;
};

View file

@ -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<net_connection_id>();
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<uint16_t>(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<uint16_t>(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";
}
}

View file

@ -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<std::string> 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<PeerlistEntry>();
pe.id = Crypto::rand<uint64_t>();
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;
}

View file

@ -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<std::string>();
}
if (options.count("log-level")) {
if (options.count("log-level") != 0) {
logLevel = options["log-level"].as<size_t>();
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<std::string>();
}
if (options.count("bind-address")) {
if (options.count("bind-address") != 0 && (!options["bind-address"].defaulted() || bindAddress.empty())) {
bindAddress = options["bind-address"].as<std::string>();
}
if (options.count("bind-port")) {
if (options.count("bind-port") != 0 && (!options["bind-port"].defaulted() || bindPort == 0)) {
bindPort = options["bind-port"].as<uint16_t>();
}
if (options.count("wallet-file")) {
if (options.count("wallet-file") != 0) {
walletFile = options["wallet-file"].as<std::string>();
}
if (options.count("wallet-password")) {
if (options.count("wallet-password") != 0) {
walletPassword = options["wallet-password"].as<std::string>();
}
if (options.count("generate-wallet")) {
if (options.count("generate-wallet") != 0) {
generateNewWallet = true;
}
if (options.count("address")) {
if (options.count("address") != 0) {
printAddresses = true;
}

View file

@ -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<std::string>();
}
if (options.count("daemon-port")) {
if (options.count("daemon-port") != 0 && (!options["daemon-port"].defaulted() || daemonPort == 0)) {
daemonPort = options["daemon-port"].as<uint16_t>();
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
#pragma once
#include <future>
namespace System {
namespace Detail {
template<class T> using Future = std::future<T>;
template<class T> Future<T> async(std::function<T()>&& operation) {
return std::async(std::launch::async, std::move(operation));
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
#pragma once
#include <condition_variable>
#include <mutex>
#include <thread>
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 T> class Future {
public:
// Create a new thread, and run `operation` in it.
explicit Future(std::function<T()>&& 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<std::mutex> guard(operationMutex);
while (state == State::STARTED) {
operationCondition.wait(guard);
}
}
bool valid() const {
std::unique_lock<std::mutex> 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<std::mutex> guard(operationMutex);
state = State::COMPLETED;
operationCondition.notify_one();
}
mutable T result;
std::function<T()> procedure;
std::exception_ptr currentException;
mutable std::mutex operationMutex;
mutable std::condition_variable operationCondition;
mutable State state;
std::thread worker;
};
template<> class Future<void> {
public:
// Create a new thread, and run `operation` in it.
explicit Future(std::function<void()>&& 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<std::mutex> guard(operationMutex);
while (state == State::STARTED) {
operationCondition.wait(guard);
}
}
bool valid() const {
std::unique_lock<std::mutex> 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<std::mutex> guard(operationMutex);
state = State::COMPLETED;
operationCondition.notify_one();
}
std::function<void()> procedure;
std::exception_ptr currentException;
mutable std::mutex operationMutex;
mutable std::condition_variable operationCondition;
mutable State state;
std::thread worker;
};
template<class T> std::function<T()> async(std::function<T()>&& operation) {
return operation;
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
#pragma once
#include <future>
namespace System {
namespace Detail {
template<class T> using Future = std::future<T>;
template<class T> Future<T> async(std::function<T()>&& operation) {
return std::async(std::launch::async, std::move(operation));
}
}
}

View file

@ -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()) {
}
}

View file

@ -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();

View file

@ -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<std::string> &args/* = std::vector<std::string>()*/)
{
bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<std::string>()*/) {
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 [<number_of_threads>] - 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<std::string> &args)
{
if (args.size() != 1)
{
bool simple_wallet::set_log(const std::vector<std::string> &args) {
if (args.size() != 1) {
fail_msg_writer() << "use: set_log <log_level_number_0-4>";
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 <log_level_number_0-4>";
return true;
}
if (l > Logging::TRACE)
{
if (l > Logging::TRACE) {
fail_msg_writer() << "wrong number range, use: set_log <log_level_number_0-4>";
return true;
}
@ -497,8 +506,7 @@ bool simple_wallet::set_log(const std::vector<std::string> &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<std::error_code> errorPromise;
std::future<std::error_code> f_error = errorPromise.get_future();
auto callback = [&errorPromise](std::error_code e) {errorPromise.set_value(e); };
m_node->addObserver(static_cast<INodeRpcProxyObserver*>(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<INodeObserver*>(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<INodeObserver*>(this));
m_node->removeObserver(static_cast<INodeRpcProxyObserver*>(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<INodeObserver*>(this));
m_wallet->addObserver(this);
try
{
try {
m_initResultPromise.reset(new std::promise<std::error_code>());
std::future<std::error_code> 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<std::string> &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<std::string> &args) {
return true;
}
bool simple_wallet::start_mining(const std::vector<std::string>& args)
{
bool simple_wallet::start_mining(const std::vector<std::string>& 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<unsigned>(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 [<number_of_threads>], " <<
"<number_of_threads> should be from 1 to " << max_mining_threads_count;
return true;
@ -769,6 +776,8 @@ bool simple_wallet::start_mining(const std::vector<std::string>& 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<std::string>& 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<std::string>& args/* = std::vector<std::string>()*/)
{
bool simple_wallet::show_balance(const std::vector<std::string>& args/* = std::vector<std::string>()*/) {
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<std::string>& args)
{
bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& 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<std::string>& args) {
return true;
}
bool simple_wallet::show_payments(const std::vector<std::string> &args)
{
if (args.empty())
{
bool simple_wallet::show_payments(const std::vector<std::string> &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<std::string> &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<std::string> &args)
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::show_blockchain_height(const std::vector<std::string>& args)
{
bool simple_wallet::show_blockchain_height(const std::vector<std::string>& 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<std::string>& args)
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::transfer(const std::vector<std::string> &args)
{
try
{
bool simple_wallet::transfer(const std::vector<std::string> &args) {
try {
TransferCommand cmd(m_currency);
if (!cmd.parseArguments(logger, args))
@ -989,17 +998,11 @@ bool simple_wallet::transfer(const std::vector<std::string> &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<std::string> &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;
}

View file

@ -23,12 +23,12 @@
#include <boost/program_options/variables_map.hpp>
#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 <Logging/LoggerRef.h>
@ -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<CryptoNote::INode> m_node;
std::unique_ptr<CryptoNote::NodeRpcProxy> m_node;
std::unique_ptr<CryptoNote::IWalletLegacy> m_wallet;
refresh_progress_reporter_t m_refresh_progress_reporter;
};

102
src/System/RemoteContext.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
#pragma once
#include <cassert>
#include <System/Dispatcher.h>
#include <System/Event.h>
#include <System/Future.h>
#include <System/InterruptedException.h>
namespace System {
template<class T = void> class RemoteContext {
public:
// Start a thread, execute operation in it, continue execution of current context.
RemoteContext(Dispatcher& d, std::function<T()>&& operation)
: dispatcher(d), event(d), procedure(std::move(operation)), future(System::Detail::async<T>([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<T()> procedure;
mutable System::Detail::Future<T> future;
mutable bool interrupted;
};
}

View file

@ -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 <http://www.gnu.org/licenses/>.
#pragma once
#include <cstdint>
#include <utility>
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;
};
}

View file

@ -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";
}
}

View file

@ -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<RandomAccessIndex>().size()) {
throw std::system_error(std::make_error_code(std::errc::invalid_argument));
}
const WalletRecord& wallet = m_walletsContainer.get<RandomAccessIndex>()[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<uint64_t>(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<BlockHeightIndex>();
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

View file

@ -21,6 +21,7 @@
#include <queue>
#include "IFusionManager.h"
#include "WalletIndices.h"
#include <System/Dispatcher.h>
@ -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<WalletOuts> 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<OutputToTransfer>& selectedTransfers);
void deleteSpentOutputs(const Crypto::Hash& transactionHash);

View file

@ -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 ")"

View file

@ -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 <http://www.gnu.org/licenses/>.
#include <System/RemoteContext.h>
#include <System/Dispatcher.h>
#include <System/ContextGroup.h>
#include <System/Event.h>
#include <System/InterruptedException.h>
#include <System/Timer.h>
#include <gtest/gtest.h>
using namespace System;
class RemoteContextTests : public testing::Test {
public:
Dispatcher dispatcher;
};
TEST_F(RemoteContextTests, getReturnsResult) {
RemoteContext<int> 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<int>(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<std::chrono::milliseconds>(end - start).count(), 5);
ASSERT_LE(std::chrono::duration_cast<std::chrono::milliseconds>(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<std::chrono::milliseconds>(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<std::chrono::milliseconds>(end - start).count(), 10);
}

View file

@ -79,8 +79,11 @@ public:
void setGetNewBlocksLimit(size_t maxBlocks) { m_getMaxBlocks = maxBlocks; }
virtual uint32_t getLastLocalBlockHeight() const { return static_cast<uint32_t>(m_blockchainGenerator.getBlockchain().size() - 1); };
virtual uint32_t getLastKnownBlockHeight() const { return static_cast<uint32_t>(m_blockchainGenerator.getBlockchain().size() - 1); };
virtual uint32_t getLastLocalBlockHeight() const { return static_cast<uint32_t>(m_blockchainGenerator.getBlockchain().size() - 1); }
virtual uint32_t getLastKnownBlockHeight() const { return static_cast<uint32_t>(m_blockchainGenerator.getBlockchain().size() - 1); }
virtual uint32_t getLocalBlockCount() const override { return static_cast<uint32_t>(m_blockchainGenerator.getBlockchain().size()); }
virtual uint32_t getKnownBlockCount() const override { return static_cast<uint32_t>(m_blockchainGenerator.getBlockchain().size()); }
virtual void getNewBlocks(std::vector<Crypto::Hash>&& knownBlockIds, std::vector<CryptoNote::block_complete_entry>& newBlocks, uint32_t& startHeight, const Callback& callback);

View file

@ -34,6 +34,7 @@
#include "WalletLegacy/WalletLegacySerializer.h"
#include <System/Dispatcher.h>
#include <System/Timer.h>
#include <System/Context.h>
using namespace Crypto;
using namespace Common;
@ -129,6 +130,8 @@ protected:
template<typename T>
void waitForValue(CryptoNote::WalletGreen& wallet, T value, std::function<T ()>&& 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)));
}