diff --git a/src/wallet/api/utils.cpp b/src/wallet/api/utils.cpp index aa85323f..1bf35197 100644 --- a/src/wallet/api/utils.cpp +++ b/src/wallet/api/utils.cpp @@ -56,18 +56,22 @@ bool isAddressLocal(const std::string &address) LOG_PRINT_L1("Failed to determine whether daemon is local, assuming not"); return false; } - - // resolve to IP - boost::asio::io_service io_service; - boost::asio::ip::tcp::resolver resolver(io_service); - boost::asio::ip::tcp::resolver::query query(u_c.host, ""); - boost::asio::ip::tcp::resolver::iterator i = resolver.resolve(query); - while (i != boost::asio::ip::tcp::resolver::iterator()) - { - const boost::asio::ip::tcp::endpoint &ep = *i; - if (ep.address().is_loopback()) - return true; - ++i; + // resolver::resolve can throw an exception + try { + // resolve to IP + boost::asio::io_service io_service; + boost::asio::ip::tcp::resolver resolver(io_service); + boost::asio::ip::tcp::resolver::query query(u_c.host, ""); + boost::asio::ip::tcp::resolver::iterator i = resolver.resolve(query); + while (i != boost::asio::ip::tcp::resolver::iterator()) + { + const boost::asio::ip::tcp::endpoint &ep = *i; + if (ep.address().is_loopback()) + return true; + ++i; + } + } catch (const boost::system::system_error &e) { + LOG_ERROR("Failed to resolve " << address << ", :" << e.what()); } return false; diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index eefb49e9..16321a5a 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -167,7 +167,7 @@ uint64_t Wallet::maximumAllowedAmount() ///////////////////////// WalletImpl implementation //////////////////////// WalletImpl::WalletImpl(bool testnet) :m_wallet(nullptr), m_status(Wallet::Status_Ok), m_trustedDaemon(false), - m_wallet2Callback(nullptr) + m_wallet2Callback(nullptr), m_recoveringFromSeed(false) { m_wallet = new tools::wallet2(testnet); m_history = new TransactionHistoryImpl(this); @@ -176,6 +176,7 @@ WalletImpl::WalletImpl(bool testnet) m_refreshThreadDone = false; m_refreshEnabled = false; + m_refreshIntervalMillis = DEFAULT_REFRESH_INTERVAL_MILLIS; m_refreshThread = boost::thread([this] () { @@ -196,7 +197,7 @@ bool WalletImpl::create(const std::string &path, const std::string &password, co { clearStatus(); - + m_recoveringFromSeed = false; bool keys_file_exists; bool wallet_file_exists; tools::wallet2::wallet_exists(path, keys_file_exists, wallet_file_exists); @@ -233,6 +234,7 @@ bool WalletImpl::create(const std::string &path, const std::string &password, co bool WalletImpl::open(const std::string &path, const std::string &password) { clearStatus(); + m_recoveringFromSeed = false; try { // TODO: handle "deprecated" m_wallet->load(path, password); @@ -257,6 +259,7 @@ bool WalletImpl::recover(const std::string &path, const std::string &seed) return false; } + m_recoveringFromSeed = true; crypto::secret_key recovery_key; std::string old_language; if (!crypto::ElectrumWords::words_to_bytes(seed, recovery_key, old_language)) { @@ -269,6 +272,7 @@ bool WalletImpl::recover(const std::string &path, const std::string &seed) m_wallet->set_seed_language(old_language); m_wallet->generate(path, "", recovery_key, true, false); // TODO: wallet->init(daemon_address); + } catch (const std::exception &e) { m_status = Status_Error; m_errorString = e.what(); @@ -385,11 +389,7 @@ string WalletImpl::keysFilename() const bool WalletImpl::init(const std::string &daemon_address, uint64_t upper_transaction_size_limit) { clearStatus(); - - m_wallet->init(daemon_address, upper_transaction_size_limit); - if (Utils::isAddressLocal(daemon_address)) { - this->setTrustedDaemon(true); - } + doInit(daemon_address, upper_transaction_size_limit); bool result = this->refresh(); // enabling background refresh thread startRefresh(); @@ -400,10 +400,7 @@ bool WalletImpl::init(const std::string &daemon_address, uint64_t upper_transact void WalletImpl::initAsync(const string &daemon_address, uint64_t upper_transaction_size_limit) { clearStatus(); - m_wallet->init(daemon_address, upper_transaction_size_limit); - if (Utils::isAddressLocal(daemon_address)) { - this->setTrustedDaemon(true); - } + doInit(daemon_address, upper_transaction_size_limit); startRefresh(); } @@ -748,4 +745,27 @@ void WalletImpl::pauseRefresh() } +bool WalletImpl::isNewWallet() const +{ + // in case wallet created without daemon connection, closed and opened again, + // it's the same case as if it created from scratch, i.e. we need "fast sync" + // with the daemon (pull hashes instead of pull blocks) + return !(blockChainHeight() > 1 || m_recoveringFromSeed); +} + +void WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction_size_limit) +{ + m_wallet->init(daemon_address, upper_transaction_size_limit); + + // in case new wallet, this will force fast-refresh (pulling hashes instead of blocks) + if (isNewWallet()) { + m_wallet->set_refresh_from_block_height(daemonBlockChainHeight()); + } + + if (Utils::isAddressLocal(daemon_address)) { + this->setTrustedDaemon(true); + } + +} + } // namespace diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index d97a8f3b..5706b2bf 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -101,6 +101,8 @@ private: void startRefresh(); void stopRefresh(); void pauseRefresh(); + bool isNewWallet() const; + void doInit(const std::string &daemon_address, uint64_t upper_transaction_size_limit); private: friend class PendingTransactionImpl; @@ -126,7 +128,10 @@ private: boost::mutex m_refreshMutex2; boost::condition_variable m_refreshCV; boost::thread m_refreshThread; - + // flag indicating wallet is recovering from seed + // so it shouldn't be considered as new and pull blocks (slow-refresh) + // instead of pulling hashes (fast-refresh) + bool m_recoveringFromSeed; }; diff --git a/tests/libwallet_api_tests/main.cpp b/tests/libwallet_api_tests/main.cpp index 87e0cc93..45a94b4d 100644 --- a/tests/libwallet_api_tests/main.cpp +++ b/tests/libwallet_api_tests/main.cpp @@ -58,6 +58,7 @@ namespace Consts // TODO: get rid of hardcoded paths const char * WALLET_NAME = "testwallet"; +const char * WALLET_NAME_MAINNET = "testwallet_mainnet"; const char * WALLET_NAME_COPY = "testwallet_copy"; const char * WALLET_NAME_WITH_DIR = "walletdir/testwallet_test"; const char * WALLET_NAME_WITH_DIR_NON_WRITABLE = "/var/walletdir/testwallet_test"; @@ -85,6 +86,7 @@ const uint64_t AMOUNT_1XMR = 1000000000000L; const std::string PAYMENT_ID_EMPTY = ""; std::string TESTNET_DAEMON_ADDRESS = "localhost:38081"; +std::string MAINNET_DAEMON_ADDRESS = "localhost:18081"; } @@ -157,6 +159,25 @@ struct WalletManagerTest : public testing::Test }; +struct WalletManagerMainnetTest : public testing::Test +{ + Bitmonero::WalletManager * wmgr; + + + WalletManagerMainnetTest() + { + std::cout << __FUNCTION__ << std::endl; + wmgr = Bitmonero::WalletManagerFactory::getWalletManager(); + Utils::deleteWallet(WALLET_NAME_MAINNET); + } + + + ~WalletManagerMainnetTest() + { + std::cout << __FUNCTION__ << std::endl; + } + +}; struct WalletTest1 : public testing::Test { @@ -809,8 +830,12 @@ struct MyWalletListener : public Bitmonero::WalletListener virtual void newBlock(uint64_t height) { - std::cout << "wallet: " << wallet->address() - <<", new block received, blockHeight: " << height << std::endl; +// std::cout << "wallet: " << wallet->address() +// <<", new block received, blockHeight: " << height << std::endl; + static int bc_height = wallet->daemonBlockChainHeight(); + std::cout << height + << " / " << bc_height/* 0*/ + << std::endl; newblock_triggered = true; cv_newblock.notify_one(); } @@ -871,6 +896,7 @@ TEST_F(WalletTest2, WalletCallBackRefreshedAsync) + TEST_F(WalletTest2, WalletCallbackSent) { @@ -924,7 +950,7 @@ TEST_F(WalletTest2, WalletCallbackReceived) ASSERT_TRUE(wallet_dst->refresh()); uint64_t balance = wallet_dst->balance(); std::cout << "** Balance dst1: " << wallet_dst->displayAmount(wallet_dst->balance()) << std::endl; - MyWalletListener * wallet_dst_listener = new MyWalletListener(wallet_dst); + std::unique_ptr wallet_dst_listener (new MyWalletListener(wallet_dst)); uint64_t amount = AMOUNT_1XMR * 5; std::cout << "** Sending " << Bitmonero::Wallet::displayAmount(amount) << " to " << wallet_dst->address(); @@ -968,7 +994,7 @@ TEST_F(WalletTest2, WalletCallbackNewBlock) std::cout << "** Block height: " << bc1 << std::endl; - MyWalletListener * wallet_listener = new MyWalletListener(wallet_src); + std::unique_ptr wallet_listener (new MyWalletListener(wallet_src)); // wait max 4 min for new block std::chrono::seconds wait_for = std::chrono::seconds(60*4); @@ -984,17 +1010,129 @@ TEST_F(WalletTest2, WalletCallbackNewBlock) } +TEST_F(WalletManagerMainnetTest, CreateOpenAndRefreshWalletMainNetSync) +{ + + Bitmonero::Wallet * wallet = wmgr->createWallet(WALLET_NAME_MAINNET, "", WALLET_LANG); + std::unique_ptr wallet_listener (new MyWalletListener(wallet)); + wallet->init(MAINNET_DAEMON_ADDRESS, 0); + std::cerr << "TEST: waiting on refresh lock...\n"; + //wallet_listener->cv_refresh.wait_for(lock, wait_for); + std::cerr << "TEST: refresh lock acquired...\n"; + ASSERT_TRUE(wallet_listener->refresh_triggered); + ASSERT_TRUE(wallet->connected()); + ASSERT_TRUE(wallet->blockChainHeight() == wallet->daemonBlockChainHeight()); + std::cerr << "TEST: closing wallet...\n"; + wmgr->closeWallet(wallet); +} + + +TEST_F(WalletManagerMainnetTest, CreateAndRefreshWalletMainNetAsync) +{ + // supposing 120 seconds should be enough for fast refresh + int SECONDS_TO_REFRESH = 120; + + Bitmonero::Wallet * wallet = wmgr->createWallet(WALLET_NAME_MAINNET, "", WALLET_LANG); + std::unique_ptr wallet_listener (new MyWalletListener(wallet)); + + std::chrono::seconds wait_for = std::chrono::seconds(SECONDS_TO_REFRESH); + std::unique_lock lock (wallet_listener->mutex); + wallet->initAsync(MAINNET_DAEMON_ADDRESS, 0); + // wallet->init(MAINNET_DAEMON_ADDRESS, 0); + std::cerr << "TEST: waiting on refresh lock...\n"; + wallet_listener->cv_refresh.wait_for(lock, wait_for); + std::cerr << "TEST: refresh lock acquired...\n"; + ASSERT_TRUE(wallet->status() == Bitmonero::Wallet::Status_Ok); + ASSERT_TRUE(wallet_listener->refresh_triggered); + ASSERT_TRUE(wallet->connected()); + ASSERT_TRUE(wallet->blockChainHeight() == wallet->daemonBlockChainHeight()); + std::cerr << "TEST: closing wallet...\n"; + wmgr->closeWallet(wallet); +} + +TEST_F(WalletManagerMainnetTest, OpenAndRefreshWalletMainNetAsync) +{ + + // supposing 120 seconds should be enough for fast refresh + int SECONDS_TO_REFRESH = 120; + Bitmonero::Wallet * wallet = wmgr->createWallet(WALLET_NAME_MAINNET, "", WALLET_LANG); + wmgr->closeWallet(wallet); + wallet = wmgr->openWallet(WALLET_NAME_MAINNET, ""); + + std::unique_ptr wallet_listener (new MyWalletListener(wallet)); + + std::chrono::seconds wait_for = std::chrono::seconds(SECONDS_TO_REFRESH); + std::unique_lock lock (wallet_listener->mutex); + wallet->initAsync(MAINNET_DAEMON_ADDRESS, 0); + // wallet->init(MAINNET_DAEMON_ADDRESS, 0); + std::cerr << "TEST: waiting on refresh lock...\n"; + wallet_listener->cv_refresh.wait_for(lock, wait_for); + std::cerr << "TEST: refresh lock acquired...\n"; + ASSERT_TRUE(wallet->status() == Bitmonero::Wallet::Status_Ok); + ASSERT_TRUE(wallet_listener->refresh_triggered); + ASSERT_TRUE(wallet->connected()); + ASSERT_TRUE(wallet->blockChainHeight() == wallet->daemonBlockChainHeight()); + std::cerr << "TEST: closing wallet...\n"; + wmgr->closeWallet(wallet); + +} + +TEST_F(WalletManagerMainnetTest, RecoverAndRefreshWalletMainNetAsync) +{ + + // supposing 120 seconds should be enough for fast refresh + int SECONDS_TO_REFRESH = 120; + Bitmonero::Wallet * wallet = wmgr->createWallet(WALLET_NAME_MAINNET, "", WALLET_LANG); + std::string seed = wallet->seed(); + std::string address = wallet->address(); + wmgr->closeWallet(wallet); + + // deleting wallet files + Utils::deleteWallet(WALLET_NAME_MAINNET); + // ..and recovering wallet from seed + + wallet = wmgr->recoveryWallet(WALLET_NAME_MAINNET, seed); + ASSERT_TRUE(wallet->status() == Bitmonero::Wallet::Status_Ok); + ASSERT_TRUE(wallet->address() == address); + std::unique_ptr wallet_listener (new MyWalletListener(wallet)); + std::chrono::seconds wait_for = std::chrono::seconds(SECONDS_TO_REFRESH); + std::unique_lock lock (wallet_listener->mutex); + wallet->initAsync(MAINNET_DAEMON_ADDRESS, 0); + // wallet->init(MAINNET_DAEMON_ADDRESS, 0); + std::cerr << "TEST: waiting on refresh lock...\n"; + + // here we wait for 120 seconds and test if wallet doesn't syncrnonize blockchain completely, + // as it needs much more than 120 seconds for mainnet + + wallet_listener->cv_refresh.wait_for(lock, wait_for); + ASSERT_TRUE(wallet->status() == Bitmonero::Wallet::Status_Ok); + ASSERT_FALSE(wallet_listener->refresh_triggered); + ASSERT_TRUE(wallet->connected()); + ASSERT_FALSE(wallet->blockChainHeight() == wallet->daemonBlockChainHeight()); + std::cerr << "TEST: closing wallet...\n"; + wmgr->closeWallet(wallet); + std::cerr << "TEST: wallet closed\n"; + +} + int main(int argc, char** argv) { // we can override default values for "TESTNET_DAEMON_ADDRESS" and "WALLETS_ROOT_DIR" - const char * monero_daemon_addr = std::getenv("TESTNET_DAEMON_ADDRESS"); - if (monero_daemon_addr) { - TESTNET_DAEMON_ADDRESS = monero_daemon_addr; + const char * testnet_daemon_addr = std::getenv("TESTNET_DAEMON_ADDRESS"); + if (testnet_daemon_addr) { + TESTNET_DAEMON_ADDRESS = testnet_daemon_addr; } + const char * mainnet_daemon_addr = std::getenv("MAINNET_DAEMON_ADDRESS"); + if (mainnet_daemon_addr) { + MAINNET_DAEMON_ADDRESS = mainnet_daemon_addr; + } + + + const char * wallets_root_dir = std::getenv("WALLETS_ROOT_DIR"); if (wallets_root_dir) { WALLETS_ROOT_DIR = wallets_root_dir;