From d1b97f3811452ede63d0e0642cca3ced7d81a358 Mon Sep 17 00:00:00 2001 From: woodser Date: Wed, 29 May 2019 16:18:50 -0400 Subject: [PATCH] add monero namespace; Wallet2Listener w/ onSyncStart(), onSyncEnd(), setWalletListener() ready to notify sync progress --- src/wallet/MoneroWallet.cpp | 531 ++++++++++++++++++++---------------- src/wallet/MoneroWallet.h | 13 +- 2 files changed, 309 insertions(+), 235 deletions(-) diff --git a/src/wallet/MoneroWallet.cpp b/src/wallet/MoneroWallet.cpp index 3bd4a60..49d2829 100644 --- a/src/wallet/MoneroWallet.cpp +++ b/src/wallet/MoneroWallet.cpp @@ -8,243 +8,314 @@ using namespace cryptonote; /** - * Listens for wallet2 notifications and notifies an external wallet listener. + * Public interface for libmonero-cpp library. */ -struct Wallet2Listener : public tools::i_wallet2_callback { +namespace monero { - Wallet2Listener(MoneroWalletListener* listener) { - this->listener = listener; - } + /** + * Listens to wallet2 notifications in order to facilitate external wallet notifications. + */ + struct Wallet2Listener : public tools::i_wallet2_callback { - ~Wallet2Listener() { + public: - } + Wallet2Listener(MoneroWallet* wallet, shared_ptr wallet2) { + this->wallet = wallet; + this->wallet2 = wallet2; + cout << "syncStartHeight: " << syncStartHeight << ", syncEndHeight: " << syncEndHeight << endl; + } - virtual void on_new_block(uint64_t height, const cryptonote::block& cnBlock) { - MoneroBlock block; - block.height = height; - listener->onNewBlock(block); - } + ~Wallet2Listener() { - MoneroWalletListener* listener; -}; + } -// ------------------------------- WALLET METHODS ----------------------------- - -bool MoneroWallet::walletExists(const string& path) { - cout << "walletExists(" << path << ")" << endl; - bool keyFileExists; - bool walletFileExists; - tools::wallet2::wallet_exists(path, keyFileExists, walletFileExists); - return walletFileExists; -} - -MoneroWallet::MoneroWallet() { - cout << "MoneroWallet()" << endl; - throw runtime_error("Not implemented"); -} - -MoneroWallet::MoneroWallet(const MoneroNetworkType networkType, const MoneroRpcConnection& daemonConnection, const string& language) { - cout << "MoneroWallet(3)" << endl; - wallet2 = unique_ptr(new tools::wallet2(static_cast(networkType), 1, true)); - MoneroWallet::setDaemonConnection(daemonConnection.uri, daemonConnection.username, daemonConnection.password); - wallet2->set_seed_language(language); - crypto::secret_key recovery_val, secret_key; - wallet2->generate(string(""), string(""), secret_key, false, false); -} - -MoneroWallet::MoneroWallet(const string& mnemonic, const MoneroNetworkType networkType, const MoneroRpcConnection& daemonConnection, uint64_t restoreHeight) { - - // validate mnemonic and get recovery key and language - crypto::secret_key recoveryKey; - std::string language; - bool isValid = crypto::ElectrumWords::words_to_bytes(mnemonic, recoveryKey, language); - if (!isValid) throw runtime_error("Invalid mnemonic"); // TODO: need proper error handling - if (language == crypto::ElectrumWords::old_language_name) language = Language::English().get_language_name(); - - // initialize wallet - wallet2 = unique_ptr(new tools::wallet2(static_cast(networkType), 1, true)); - wallet2->set_seed_language(language); - wallet2->generate(string(""), string(""), recoveryKey, true, false); - wallet2->set_refresh_from_block_height(restoreHeight); - - // print the mnemonic - epee::wipeable_string fetchedMnemonic; - wallet2->get_seed(fetchedMnemonic); - cout << "Mnemonic: " << string(fetchedMnemonic.data(), fetchedMnemonic.size()) << endl; -} - -MoneroWallet::MoneroWallet(const string& address, const string& viewKey, const string& spendKey, const MoneroNetworkType networkType, const string& daemonConnection, uint64_t restoreHeight, const string& language) { - cout << "MoneroWallet(7)" << endl; - throw runtime_error("Not implemented"); -} - -MoneroWallet::MoneroWallet(const string& path, const string& password, const MoneroNetworkType networkType) { - cout << "MoneroWallet(3b)" << endl; - wallet2 = unique_ptr(new tools::wallet2(static_cast(networkType), 1, true)); - wallet2->load(path, password); -} - -MoneroWallet::~MoneroWallet() { - cout << "~MoneroWallet()" << endl; -} - - -tools::wallet2* MoneroWallet::getWallet2() { - return wallet2.get(); -} - -// TODO: switch this to setDaemonConnection(MoneroDaemonRpc& daemonConnection) -void MoneroWallet::setDaemonConnection(const string& uri, const string& username, const string& password) { - cout << "setDaemonConnection(" << uri << ", " << username << ", " << password << ")" << endl; - - // prepare uri, login, and isTrusted for wallet2 - boost::optional login{}; - login.emplace(username, password); - bool isTrusted = false; - try { isTrusted = tools::is_local_address(uri); } // wallet is trusted iff local - catch (const exception &e) { } - - // set wallet2 daemon connection - wallet2->set_daemon(uri, login, isTrusted); -} - -MoneroRpcConnection MoneroWallet::getDaemonConnection() const { - MoneroRpcConnection connection; - if (!wallet2->get_daemon_address().empty()) connection.uri = wallet2->get_daemon_address(); - if (wallet2->get_daemon_login()) { - if (!wallet2->get_daemon_login()->username.empty()) connection.username = wallet2->get_daemon_login()->username; - epee::wipeable_string wipeablePassword = wallet2->get_daemon_login()->password; - string password = string(wipeablePassword.data(), wipeablePassword.size()); - if (!password.empty()) connection.password = password; - } - return connection; -} - -string MoneroWallet::getPath() const { - return wallet2->path(); -} - -MoneroNetworkType MoneroWallet::getNetworkType() const { - return static_cast(wallet2->nettype()); -} - -string MoneroWallet::getLanguage() const { - return wallet2->get_seed_language(); -} - -string MoneroWallet::getMnemonic() const { - epee::wipeable_string wipeablePassword; - wallet2->get_seed(wipeablePassword); - return string(wipeablePassword.data(), wipeablePassword.size()); -} - -uint64_t MoneroWallet::getHeight() const { - return wallet2->get_blockchain_current_height(); -} - -uint64_t MoneroWallet::getChainHeight() const { - string err; - return wallet2->get_daemon_blockchain_height(err); -} - -uint64_t MoneroWallet::getRestoreHeight() const { - return wallet2->get_refresh_from_block_height(); -} - -void MoneroWallet::setListener(MoneroWalletListener* listener) { - cout << "setListener()" << endl; - if (listener == nullptr) { - this->listener = nullptr; - wallet2->callback(nullptr); - } else { + void setWalletListener(MoneroWalletListener* listener) { this->listener = listener; - wallet2->callback(new Wallet2Listener(listener)); // TODO: this is memory leak next time listener is set? + updateListening(); + } + + void onSyncStart(uint64_t startHeight, MoneroSyncListener* syncListener) { + syncStartHeight = new uint64_t(startHeight); + cout << "Set start height " << startHeight << " vs " << *syncStartHeight << endl; + this->syncListener = syncListener; + updateListening(); + } + + void onSyncEnd() { + syncStartHeight = nullptr; // TODO: delete first? yes. + syncEndHeight = nullptr; + syncListener = nullptr; + updateListening(); + } + + virtual void on_new_block(uint64_t height, const cryptonote::block& cnBlock) { + + // notify listeners of block + MoneroBlock block; + block.height = height; + listener->onNewBlock(block); + + // notify listeners of sync progress + if (syncStartHeight != nullptr && height > *syncStartHeight) { + + // assign end height if necessary + if (syncEndHeight == nullptr) { + syncEndHeight = new uint64_t(wallet->getChainHeight()); + } + + // increase end height if necessary + if (height > *syncEndHeight) *syncEndHeight = height - *syncStartHeight + 1; + + // prep notification params + uint64_t numBlocksDone = height - *syncStartHeight; + uint64_t numBlocksTotal = *syncEndHeight - *syncStartHeight + 1; + double percentDone = numBlocksDone / (double) numBlocksTotal; + string message = string("Synchronizing"); + cout << "Ready to notify sync progress(" << *syncStartHeight << ", " << numBlocksDone << ", " << numBlocksTotal << ", " << percentDone << ", " << message << ")" << endl; + } + } + + private: + MoneroWallet* wallet; + shared_ptr wallet2; + MoneroWalletListener* listener; + uint64_t* syncStartHeight; + uint64_t* syncEndHeight; + MoneroSyncListener* syncListener; + + void updateListening() { + wallet2->callback(listener == nullptr && syncListener == nullptr ? nullptr : this); + } + }; + + // ------------------------------- WALLET METHODS ----------------------------- + + bool MoneroWallet::walletExists(const string& path) { + cout << "walletExists(" << path << ")" << endl; + bool keyFileExists; + bool walletFileExists; + tools::wallet2::wallet_exists(path, keyFileExists, walletFileExists); + return walletFileExists; } -} - -MoneroSyncResult MoneroWallet::sync() { - cout << "sync()" << endl; - return syncAux(nullptr, nullptr, nullptr); -} - -MoneroSyncResult MoneroWallet::sync(MoneroSyncListener& listener) { - cout << "sync(startHeight)" << endl; - return syncAux(nullptr, nullptr, &listener); -} - -MoneroSyncResult MoneroWallet::sync(uint64_t startHeight) { - cout << "sync(startHeight)" << endl; - return syncAux(&startHeight, nullptr, nullptr); -} - -MoneroSyncResult MoneroWallet::sync(uint64_t startHeight, MoneroSyncListener& listener) { - cout << "sync(startHeight, listener)" << endl; - return syncAux(&startHeight, nullptr, &listener); -} - -string MoneroWallet::getAddress(uint32_t accountIdx, uint32_t subaddressIdx) const { - return wallet2->get_subaddress_as_str({accountIdx, subaddressIdx}); -} - -// ------------------------------- PRIVATE HELPERS ---------------------------- - -MoneroSyncResult MoneroWallet::syncAux(uint64_t* startHeight, uint64_t* endHeight, MoneroSyncListener* listener) { - cout << "syncAux()" << endl; - - // validate inputs - if (endHeight != nullptr) throw runtime_error("Monero core wallet does not support syncing to an end height"); // TODO: custom exception type - - // save start height, end height, and listener for sync notifications - // TODO monero-core: support sync notifications - syncStartHeight = new uint64_t(startHeight == nullptr ? max(MoneroWallet::getHeight(), MoneroWallet::getRestoreHeight()) : *startHeight); - syncEndHeight = new uint64_t(MoneroWallet::getChainHeight()); - if (listener != nullptr) syncListener = unique_ptr(listener); - - // sync wallet - MoneroSyncResult result; - wallet2->refresh(wallet2->is_trusted_daemon(), *syncStartHeight, result.numBlocksFetched, result.receivedMoney, true); - - // clear sync state variables - delete syncStartHeight; - syncStartHeight = nullptr; - delete syncEndHeight; - syncEndHeight = nullptr; - syncListener.reset(); - return result; - -// if (startHeight == null) startHeight = Math.max(getHeight(), getRestoreHeight()); -// if (endHeight != null) throw new MoneroException("Monero core wallet does not support syncing to an end height"); -// -// // verify connection to daemon which informs sync end height -// MoneroDaemonRpc daemon = new MoneroDaemonRpc(getDaemonConnection()); -// assertTrue("No connection to daemon", daemon.getIsConnected()); // TODO: way to get end height from wallet2? need to fallback if daemon not connected, let wallet report sync error -// -// // wrap and register sync listener as normal wallet listener -// SyncListenerWrapper syncListenerWrapper = new SyncListenerWrapper(listener); -// addListener(syncListenerWrapper); -// -// // register listener which notifies all listeners of sync updates -// SyncNotifier syncNotifier = new SyncNotifier(startHeight, getChainHeight() - 1); -// addListener(syncNotifier); -// -// // listen for new blocks added to the chain in order to update sync height // TODO: no way to get this from wallet2? -// MoneroDaemonListener syncRangeUpdater = new MoneroDaemonListener() { -// public void onBlockHeader(MoneroBlockHeader header) { -// syncNotifier.setEndHeight(header.getHeight()); -// } -// }; -// daemon.addListener(syncRangeUpdater); -// -// // sync wallet -// syncNotifier.onStart(); // notify sync listeners of 0% progress -// Object[] results = syncJni(startHeight); -// -// // unregister listeners -// removeListener(syncNotifier); -// removeListener(syncListenerWrapper); -// daemon.removeListener(syncRangeUpdater); -// -// // return results -// return new MoneroSyncResult((long) results[0], (boolean) results[1]); + + MoneroWallet::MoneroWallet() { + cout << "MoneroWallet()" << endl; + throw runtime_error("Not implemented"); + } + + MoneroWallet::MoneroWallet(const MoneroNetworkType networkType, const MoneroRpcConnection& daemonConnection, const string& language) { + cout << "MoneroWallet(3)" << endl; + wallet2 = shared_ptr(new tools::wallet2(static_cast(networkType), 1, true)); + MoneroWallet::setDaemonConnection(daemonConnection.uri, daemonConnection.username, daemonConnection.password); + wallet2->set_seed_language(language); + crypto::secret_key recovery_val, secret_key; + wallet2->generate(string(""), string(""), secret_key, false, false); + wallet2Listener = unique_ptr(new Wallet2Listener(this, wallet2)); + } + + MoneroWallet::MoneroWallet(const string& mnemonic, const MoneroNetworkType networkType, const MoneroRpcConnection& daemonConnection, uint64_t restoreHeight) { + + // validate mnemonic and get recovery key and language + crypto::secret_key recoveryKey; + std::string language; + bool isValid = crypto::ElectrumWords::words_to_bytes(mnemonic, recoveryKey, language); + if (!isValid) throw runtime_error("Invalid mnemonic"); // TODO: need proper error handling + if (language == crypto::ElectrumWords::old_language_name) language = Language::English().get_language_name(); + + // initialize wallet + wallet2 = shared_ptr(new tools::wallet2(static_cast(networkType), 1, true)); + wallet2->set_seed_language(language); + wallet2->generate(string(""), string(""), recoveryKey, true, false); + wallet2->set_refresh_from_block_height(restoreHeight); + wallet2Listener = unique_ptr(new Wallet2Listener(this, wallet2)); + + // print the mnemonic + epee::wipeable_string fetchedMnemonic; + wallet2->get_seed(fetchedMnemonic); + cout << "Mnemonic: " << string(fetchedMnemonic.data(), fetchedMnemonic.size()) << endl; + } + + MoneroWallet::MoneroWallet(const string& address, const string& viewKey, const string& spendKey, const MoneroNetworkType networkType, const string& daemonConnection, uint64_t restoreHeight, const string& language) { + cout << "MoneroWallet(7)" << endl; + throw runtime_error("Not implemented"); + } + + MoneroWallet::MoneroWallet(const string& path, const string& password, const MoneroNetworkType networkType) { + cout << "MoneroWallet(3b)" << endl; + wallet2 = shared_ptr(new tools::wallet2(static_cast(networkType), 1, true)); + wallet2->load(path, password); + } + + MoneroWallet::~MoneroWallet() { + cout << "~MoneroWallet()" << endl; + } + + + tools::wallet2* MoneroWallet::getWallet2() { + return wallet2.get(); + } + + // TODO: switch this to setDaemonConnection(MoneroDaemonRpc& daemonConnection) + void MoneroWallet::setDaemonConnection(const string& uri, const string& username, const string& password) { + cout << "setDaemonConnection(" << uri << ", " << username << ", " << password << ")" << endl; + + // prepare uri, login, and isTrusted for wallet2 + boost::optional login{}; + login.emplace(username, password); + bool isTrusted = false; + try { isTrusted = tools::is_local_address(uri); } // wallet is trusted iff local + catch (const exception &e) { } + + // set wallet2 daemon connection + wallet2->set_daemon(uri, login, isTrusted); + } + + MoneroRpcConnection MoneroWallet::getDaemonConnection() const { + MoneroRpcConnection connection; + if (!wallet2->get_daemon_address().empty()) connection.uri = wallet2->get_daemon_address(); + if (wallet2->get_daemon_login()) { + if (!wallet2->get_daemon_login()->username.empty()) connection.username = wallet2->get_daemon_login()->username; + epee::wipeable_string wipeablePassword = wallet2->get_daemon_login()->password; + string password = string(wipeablePassword.data(), wipeablePassword.size()); + if (!password.empty()) connection.password = password; + } + return connection; + } + + string MoneroWallet::getPath() const { + return wallet2->path(); + } + + MoneroNetworkType MoneroWallet::getNetworkType() const { + return static_cast(wallet2->nettype()); + } + + string MoneroWallet::getLanguage() const { + return wallet2->get_seed_language(); + } + + string MoneroWallet::getMnemonic() const { + epee::wipeable_string wipeablePassword; + wallet2->get_seed(wipeablePassword); + return string(wipeablePassword.data(), wipeablePassword.size()); + } + + uint64_t MoneroWallet::getHeight() const { + return wallet2->get_blockchain_current_height(); + } + + uint64_t MoneroWallet::getChainHeight() const { + string err; + return wallet2->get_daemon_blockchain_height(err); + } + + uint64_t MoneroWallet::getRestoreHeight() const { + return wallet2->get_refresh_from_block_height(); + } + + void MoneroWallet::setListener(MoneroWalletListener* listener) { + cout << "setListener()" << endl; + wallet2Listener->setWalletListener(listener); + } + + MoneroSyncResult MoneroWallet::sync() { + cout << "sync()" << endl; + return syncAux(nullptr, nullptr, nullptr); + } + + MoneroSyncResult MoneroWallet::sync(MoneroSyncListener& listener) { + cout << "sync(startHeight)" << endl; + return syncAux(nullptr, nullptr, &listener); + } + + MoneroSyncResult MoneroWallet::sync(uint64_t startHeight) { + cout << "sync(startHeight)" << endl; + return syncAux(&startHeight, nullptr, nullptr); + } + + MoneroSyncResult MoneroWallet::sync(uint64_t startHeight, MoneroSyncListener& listener) { + cout << "sync(startHeight, listener)" << endl; + return syncAux(&startHeight, nullptr, &listener); + } + + string MoneroWallet::getAddress(uint32_t accountIdx, uint32_t subaddressIdx) const { + return wallet2->get_subaddress_as_str({accountIdx, subaddressIdx}); + } + + // ------------------------------- PRIVATE HELPERS ---------------------------- + + MoneroSyncResult MoneroWallet::syncAux(uint64_t* startHeight, uint64_t* endHeight, MoneroSyncListener* listener) { + cout << "syncAux()" << endl; + + // validate inputs + if (endHeight != nullptr) throw runtime_error("Monero core wallet does not support syncing to an end height"); // TODO: custom exception type + + // determine sync start height + uint64_t syncStartHeight = startHeight == nullptr ? max(MoneroWallet::getHeight(), MoneroWallet::getRestoreHeight()) : *startHeight; + + // sync wallet + wallet2Listener->onSyncStart(syncStartHeight, listener); + MoneroSyncResult result; + wallet2->refresh(wallet2->is_trusted_daemon(), syncStartHeight, result.numBlocksFetched, result.receivedMoney, true); + wallet2Listener->onSyncEnd(); + return result; + + + + + + // // save start height, end height, and listener for sync notifications + // // TODO monero-core: support sync notifications + // syncStartHeight = new uint64_t(startHeight == nullptr ? max(MoneroWallet::getHeight(), MoneroWallet::getRestoreHeight()) : *startHeight); + // syncEndHeight = new uint64_t(MoneroWallet::getChainHeight()); + // if (listener != nullptr) syncListener = unique_ptr(listener); + // + // // sync wallet + // MoneroSyncResult result; + // wallet2->refresh(wallet2->is_trusted_daemon(), *syncStartHeight, result.numBlocksFetched, result.receivedMoney, true); + // + // // clear sync state variables + // delete syncStartHeight; + // syncStartHeight = nullptr; + // delete syncEndHeight; + // syncEndHeight = nullptr; + // syncListener.reset(); + // return result; + + // if (startHeight == null) startHeight = Math.max(getHeight(), getRestoreHeight()); + // if (endHeight != null) throw new MoneroException("Monero core wallet does not support syncing to an end height"); + // + // // verify connection to daemon which informs sync end height + // MoneroDaemonRpc daemon = new MoneroDaemonRpc(getDaemonConnection()); + // assertTrue("No connection to daemon", daemon.getIsConnected()); // TODO: way to get end height from wallet2? need to fallback if daemon not connected, let wallet report sync error + // + // // wrap and register sync listener as normal wallet listener + // SyncListenerWrapper syncListenerWrapper = new SyncListenerWrapper(listener); + // addListener(syncListenerWrapper); + // + // // register listener which notifies all listeners of sync updates + // SyncNotifier syncNotifier = new SyncNotifier(startHeight, getChainHeight() - 1); + // addListener(syncNotifier); + // + // // listen for new blocks added to the chain in order to update sync height // TODO: no way to get this from wallet2? + // MoneroDaemonListener syncRangeUpdater = new MoneroDaemonListener() { + // public void onBlockHeader(MoneroBlockHeader header) { + // syncNotifier.setEndHeight(header.getHeight()); + // } + // }; + // daemon.addListener(syncRangeUpdater); + // + // // sync wallet + // syncNotifier.onStart(); // notify sync listeners of 0% progress + // Object[] results = syncJni(startHeight); + // + // // unregister listeners + // removeListener(syncNotifier); + // removeListener(syncListenerWrapper); + // daemon.removeListener(syncRangeUpdater); + // + // // return results + // return new MoneroSyncResult((long) results[0], (boolean) results[1]); + } + } diff --git a/src/wallet/MoneroWallet.h b/src/wallet/MoneroWallet.h index d509e3d..25d4847 100644 --- a/src/wallet/MoneroWallet.h +++ b/src/wallet/MoneroWallet.h @@ -5,11 +5,15 @@ using namespace std; using namespace monero; using namespace crypto; + + /** * Public interface for libmonero-cpp library. */ namespace monero { + struct Wallet2Listener; + // ----------------------------------- MODEL -------------------------------- /** @@ -252,11 +256,10 @@ namespace monero { // --------------------------------- PRIVATE -------------------------------- private: - unique_ptr wallet2; // internal wallet implementation - MoneroWalletListener* listener; // wallet's assigned listener - uint64_t* syncStartHeight; // start height when sync is invoked for notifications - uint64_t* syncEndHeight; // current end height of sync - unique_ptr syncListener; // listener when sync is invoked for notifications + friend struct Wallet2Listener; + shared_ptr wallet2; // internal wallet implementation + unique_ptr wallet2Listener; // listener for internal wallet implementation + MoneroWalletListener* listener; // wallet's external listener MoneroSyncResult syncAux(uint64_t* startHeight, uint64_t* endHeight, MoneroSyncListener* listener); };