diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/simplewallet/simplewallet.cpp | 32 | ||||
-rw-r--r-- | src/wallet/CMakeLists.txt | 21 | ||||
-rw-r--r-- | src/wallet/api/wallet.cpp | 148 | ||||
-rw-r--r-- | src/wallet/api/wallet.h | 30 | ||||
-rw-r--r-- | src/wallet/api/wallet_manager.cpp | 5 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 223 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 13 | ||||
-rw-r--r-- | src/wallet/wallet2_api.h | 77 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.cpp | 27 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server_commands_defs.h | 4 |
10 files changed, 496 insertions, 84 deletions
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 10dc9d509..eb763b0cd 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -3083,6 +3083,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) bool out = true; bool pending = true; bool failed = true; + bool pool = true; uint64_t min_height = 0; uint64_t max_height = (uint64_t)-1; @@ -3100,7 +3101,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) local_args.erase(local_args.begin()); } else if (local_args[0] == "out" || local_args[0] == "outgoing") { - in = false; + in = pool = false; local_args.erase(local_args.begin()); } else if (local_args[0] == "pending") { @@ -3108,7 +3109,11 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) local_args.erase(local_args.begin()); } else if (local_args[0] == "failed") { - in = out = pending = false; + in = out = pending = pool = false; + local_args.erase(local_args.begin()); + } + else if (local_args[0] == "pool") { + in = out = pending = failed = false; local_args.erase(local_args.begin()); } else if (local_args[0] == "all" || local_args[0] == "both") { @@ -3183,6 +3188,27 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) ((unsigned long long)i->first) % (i->second.first ? tr("in") : tr("out")) % i->second.second; } + if (pool) { + try + { + m_wallet->update_pool_state(); + std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments; + m_wallet->get_unconfirmed_payments(payments); + for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { + const tools::wallet2::payment_details &pd = i->second; + std::string payment_id = string_tools::pod_to_hex(i->first); + if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) + payment_id = payment_id.substr(0,16); + std::string note = m_wallet->get_tx_note(pd.m_tx_hash); + message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %s %s") % "pool" % "in" % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % "-" % note).str(); + } + } + catch (...) + { + fail_msg_writer() << "Failed to get pool state"; + } + } + // print unconfirmed last if (pending || failed) { std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> upayments; @@ -3238,6 +3264,8 @@ bool simple_wallet::run() // check and display warning, but go on anyway try_connect_to_daemon(); + refresh_main(0, false); + std::string addr_start = m_wallet->get_account().get_public_address_str(m_wallet->testnet()).substr(0, 6); m_auto_refresh_run = m_wallet->auto_refresh(); if (m_auto_refresh_run) diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index 0b8fe4cb1..086758c39 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -75,13 +75,26 @@ target_link_libraries(wallet ${Boost_REGEX_LIBRARY} ${EXTRA_LIBRARIES}) + +# in case of static build, randon.c.obj from UNBOUND_LIBARY conflicts with the same file from 'crypto' +# and in case of dynamic build, merge_static_libs called with ${UNBOUND_LIBRARY} will fail +if (STATIC) + set(libs_to_merge wallet cryptonote_core mnemonics common crypto) + # hack - repack libunbound into another static lib - there's conflicting object file "random.c.obj" + merge_static_libs(wallet_merged "${libs_to_merge}") + merge_static_libs(wallet_merged2 "${UNBOUND_LIBRARY}") + install(TARGETS wallet_merged wallet_merged2 + ARCHIVE DESTINATION lib) +else (STATIC) + set(libs_to_merge wallet cryptonote_core mnemonics common crypto ${UNBOUND_LIBRARY} ) + merge_static_libs(wallet_merged "${libs_to_merge}") + install(TARGETS wallet_merged + ARCHIVE DESTINATION lib) +endif (STATIC) -set(libs_to_merge wallet cryptonote_core mnemonics common crypto) #MERGE_STATIC_LIBS(wallet_merged wallet_merged "${libs_to_merge}") -merge_static_libs(wallet_merged "${libs_to_merge}") -install(TARGETS wallet_merged - ARCHIVE DESTINATION lib) + install(FILES ${wallet_api_headers} DESTINATION include/wallet) diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index be379cb99..67d2ebecb 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -46,6 +46,7 @@ namespace Bitmonero { namespace { // copy-pasted from simplewallet static const size_t DEFAULT_MIXIN = 4; + static const int DEFAULT_REFRESH_INTERVAL_SECONDS = 10; } struct Wallet2CallbackImpl : public tools::i_wallet2_callback @@ -89,6 +90,7 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback << ", amount: " << print_money(amount)); if (m_listener) { m_listener->moneyReceived(tx_hash, amount); + m_listener->updated(); } } @@ -103,6 +105,7 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback << ", amount: " << print_money(amount)); if (m_listener) { m_listener->moneySpent(tx_hash, amount); + m_listener->updated(); } } @@ -118,6 +121,7 @@ Wallet::~Wallet() {} WalletListener::~WalletListener() {} + string Wallet::displayAmount(uint64_t amount) { return cryptonote::print_money(amount); @@ -144,6 +148,12 @@ std::string Wallet::genPaymentId() } +bool Wallet::paymentIdValid(const string &paiment_id) +{ + crypto::hash8 pid; + return tools::wallet2::parse_short_payment_id(paiment_id, pid); +} + ///////////////////////// WalletImpl implementation //////////////////////// WalletImpl::WalletImpl(bool testnet) @@ -153,13 +163,22 @@ WalletImpl::WalletImpl(bool testnet) m_wallet = new tools::wallet2(testnet); m_history = new TransactionHistoryImpl(this); m_wallet2Callback = new Wallet2CallbackImpl; + m_wallet->callback(m_wallet2Callback); + m_refreshThreadDone = false; + m_refreshEnabled = false; + m_refreshIntervalSeconds = DEFAULT_REFRESH_INTERVAL_SECONDS; + m_refreshThread = std::thread([this] () { + this->refreshThreadFunc(); + }); + } WalletImpl::~WalletImpl() { - delete m_wallet2Callback; + stopRefresh(); delete m_history; delete m_wallet; + delete m_wallet2Callback; } bool WalletImpl::create(const std::string &path, const std::string &password, const std::string &language) @@ -251,8 +270,12 @@ bool WalletImpl::close() clearStatus(); bool result = false; try { + // LOG_PRINT_L0("Calling wallet::store..."); m_wallet->store(); + // LOG_PRINT_L0("wallet::store done"); + // LOG_PRINT_L0("Calling wallet::stop..."); m_wallet->stop(); + // LOG_PRINT_L0("wallet::stop done"); result = true; } catch (const std::exception &e) { m_status = Status_Error; @@ -348,21 +371,30 @@ string WalletImpl::keysFilename() const bool WalletImpl::init(const std::string &daemon_address, uint64_t upper_transaction_size_limit) { clearStatus(); - try { - m_wallet->init(daemon_address, upper_transaction_size_limit); - if (Utils::isAddressLocal(daemon_address)) { - this->setTrustedDaemon(true); - } - } catch (const std::exception &e) { - LOG_ERROR("Error initializing wallet: " << e.what()); - m_status = Status_Error; - m_errorString = e.what(); + m_wallet->init(daemon_address, upper_transaction_size_limit); + if (Utils::isAddressLocal(daemon_address)) { + this->setTrustedDaemon(true); } + bool result = this->refresh(); + // enabling background refresh thread + startRefresh(); + return result; - return m_status == Status_Ok; } +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); + } + startRefresh(); +} + + + uint64_t WalletImpl::balance() const { return m_wallet->balance(); @@ -377,15 +409,17 @@ uint64_t WalletImpl::unlockedBalance() const bool WalletImpl::refresh() { clearStatus(); - try { - m_wallet->refresh(); - } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); - } + doRefresh(); return m_status == Status_Ok; } +void WalletImpl::refreshAsync() +{ + LOG_PRINT_L3(__FUNCTION__ << ": Refreshing asyncronously.."); + clearStatus(); + m_refreshCV.notify_one(); +} + // TODO: // 1 - properly handle payment id (add another menthod with explicit 'payment_id' param) // 2 - check / design how "Transaction" can be single interface @@ -396,7 +430,9 @@ bool WalletImpl::refresh() // - unconfirmed_transfer_details; // - confirmed_transfer_details) -PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const string &payment_id, uint64_t amount, uint32_t mixin_count) +PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const string &payment_id, uint64_t amount, uint32_t mixin_count, + PendingTransaction::Priority priority) + { clearStatus(); vector<cryptonote::tx_destination_entry> dsts; @@ -458,8 +494,10 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const //std::vector<tools::wallet2::pending_tx> ptx_vector; try { + // priority called "fee_multiplied in terms of underlying wallet interface transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, - 0 /* unused fee arg*/, extra, m_trustedDaemon); + static_cast<uint64_t>(priority), + extra, m_trustedDaemon); } catch (const tools::error::daemon_busy&) { // TODO: make it translatable with "tr"? @@ -564,10 +602,17 @@ bool WalletImpl::connectToDaemon() m_status = result ? Status_Ok : Status_Error; if (!result) { m_errorString = "Error connecting to daemon at " + m_wallet->get_daemon_address(); + } else { + // start refreshing here } return result; } +bool WalletImpl::connected() const +{ + return m_wallet->check_connection(); +} + void WalletImpl::setTrustedDaemon(bool arg) { m_trustedDaemon = arg; @@ -584,5 +629,70 @@ void WalletImpl::clearStatus() m_errorString.clear(); } +void WalletImpl::refreshThreadFunc() +{ + LOG_PRINT_L3(__FUNCTION__ << ": starting refresh thread"); + + while (true) { + std::unique_lock<std::mutex> lock(m_refreshMutex); + if (m_refreshThreadDone) { + break; + } + LOG_PRINT_L3(__FUNCTION__ << ": waiting for refresh..."); + m_refreshCV.wait_for(lock, std::chrono::seconds(m_refreshIntervalSeconds)); + LOG_PRINT_L3(__FUNCTION__ << ": refresh lock acquired..."); + LOG_PRINT_L3(__FUNCTION__ << ": m_refreshEnabled: " << m_refreshEnabled); + LOG_PRINT_L3(__FUNCTION__ << ": m_status: " << m_status); + if (m_refreshEnabled /*&& m_status == Status_Ok*/) { + LOG_PRINT_L3(__FUNCTION__ << ": refreshing..."); + doRefresh(); + } + } + LOG_PRINT_L3(__FUNCTION__ << ": refresh thread stopped"); +} + +void WalletImpl::doRefresh() +{ + // synchronizing async and sync refresh calls + std::lock_guard<std::mutex> guarg(m_refreshMutex2); + try { + m_wallet->refresh(); + } catch (const std::exception &e) { + m_status = Status_Error; + m_errorString = e.what(); + } + if (m_wallet2Callback->getListener()) { + m_wallet2Callback->getListener()->refreshed(); + } +} + + +void WalletImpl::startRefresh() +{ + if (!m_refreshEnabled) { + m_refreshEnabled = true; + m_refreshCV.notify_one(); + } +} + + + +void WalletImpl::stopRefresh() +{ + if (!m_refreshThreadDone) { + m_refreshEnabled = false; + m_refreshThreadDone = true; + m_refreshThread.join(); + } +} + +void WalletImpl::pauseRefresh() +{ + // TODO synchronize access + if (!m_refreshThreadDone) { + m_refreshEnabled = false; + } +} + } // namespace diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 164aede1a..9a290e0bc 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -35,6 +35,9 @@ #include "wallet/wallet2.h" #include <string> +#include <thread> +#include <mutex> +#include <condition_variable> namespace Bitmonero { @@ -65,14 +68,19 @@ public: std::string filename() const; std::string keysFilename() const; bool init(const std::string &daemon_address, uint64_t upper_transaction_size_limit); + void initAsync(const std::string &daemon_address, uint64_t upper_transaction_size_limit); bool connectToDaemon(); + bool connected() const; void setTrustedDaemon(bool arg); bool trustedDaemon() const; uint64_t balance() const; uint64_t unlockedBalance() const; bool refresh(); + void refreshAsync(); PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, - uint64_t amount, uint32_t mixin_count); + uint64_t amount, uint32_t mixin_count, + PendingTransaction::Priority priority = PendingTransaction::Priority_Low); + virtual void disposeTransaction(PendingTransaction * t); virtual TransactionHistory * history() const; virtual void setListener(WalletListener * l); @@ -81,19 +89,37 @@ public: private: void clearStatus(); + void refreshThreadFunc(); + void doRefresh(); + void startRefresh(); + void stopRefresh(); + void pauseRefresh(); private: friend class PendingTransactionImpl; friend class TransactionHistoryImpl; tools::wallet2 * m_wallet; - int m_status; + std::atomic<int> m_status; std::string m_errorString; std::string m_password; TransactionHistoryImpl * m_history; bool m_trustedDaemon; WalletListener * m_walletListener; Wallet2CallbackImpl * m_wallet2Callback; + + // multi-threaded refresh stuff + std::atomic<bool> m_refreshEnabled; + std::atomic<bool> m_refreshThreadDone; + std::atomic<int> m_refreshIntervalSeconds; + // synchronizing refresh loop; + std::mutex m_refreshMutex; + + // synchronizing sync and async refresh + std::mutex m_refreshMutex2; + std::condition_variable m_refreshCV; + std::thread m_refreshThread; + }; diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index bf072ccca..ca83806ff 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -137,6 +137,11 @@ WalletManager *WalletManagerFactory::getWalletManager() return g_walletManager; } +void WalletManagerFactory::setLogLevel(int level) +{ + epee::log_space::log_singletone::get_set_log_detalisation_level(true, level); +} + } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index d18681f36..201f0a1f4 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -186,7 +186,7 @@ void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const cryp error = false; } //---------------------------------------------------------------------------------------------------- -void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_t height, uint64_t ts, bool miner_tx) +void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_t height, uint64_t ts, bool miner_tx, bool pool) { if (!miner_tx) process_unconfirmed(tx, height); @@ -318,16 +318,19 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ //usually we have only one transfer for user in transaction cryptonote::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request req = AUTO_VAL_INIT(req); cryptonote::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response res = AUTO_VAL_INIT(res); - req.txid = get_transaction_hash(tx); - m_daemon_rpc_mutex.lock(); - bool r = net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/get_o_indexes.bin", req, res, m_http_client, WALLET_RCP_CONNECTION_TIMEOUT); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_o_indexes.bin"); - THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_o_indexes.bin"); - THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_out_indices_error, res.status); - THROW_WALLET_EXCEPTION_IF(res.o_indexes.size() != tx.vout.size(), error::wallet_internal_error, - "transactions outputs size=" + std::to_string(tx.vout.size()) + - " not match with COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES response size=" + std::to_string(res.o_indexes.size())); + if (!pool) + { + req.txid = get_transaction_hash(tx); + m_daemon_rpc_mutex.lock(); + bool r = net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/get_o_indexes.bin", req, res, m_http_client, WALLET_RCP_CONNECTION_TIMEOUT); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_o_indexes.bin"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_o_indexes.bin"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_out_indices_error, res.status); + THROW_WALLET_EXCEPTION_IF(res.o_indexes.size() != tx.vout.size(), error::wallet_internal_error, + "transactions outputs size=" + std::to_string(tx.vout.size()) + + " not match with COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES response size=" + std::to_string(res.o_indexes.size())); + } BOOST_FOREACH(size_t o, outs) { @@ -347,18 +350,21 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ + ", m_transfers.size() is " + boost::lexical_cast<std::string>(m_transfers.size())); if (kit == m_key_images.end()) { - m_transfers.push_back(boost::value_initialized<transfer_details>()); - transfer_details& td = m_transfers.back(); - td.m_block_height = height; - td.m_internal_output_index = o; - td.m_global_output_index = res.o_indexes[o]; - td.m_tx = tx; - td.m_key_image = ki; - td.m_spent = false; - m_key_images[td.m_key_image] = m_transfers.size()-1; - LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx)); - if (0 != m_callback) - m_callback->on_money_received(height, td.m_tx, td.m_internal_output_index); + if (!pool) + { + m_transfers.push_back(boost::value_initialized<transfer_details>()); + transfer_details& td = m_transfers.back(); + td.m_block_height = height; + td.m_internal_output_index = o; + td.m_global_output_index = res.o_indexes[o]; + td.m_tx = tx; + td.m_key_image = ki; + td.m_spent = false; + m_key_images[td.m_key_image] = m_transfers.size()-1; + LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx)); + if (0 != m_callback) + m_callback->on_money_received(height, td.m_tx, td.m_internal_output_index); + } } else if (m_transfers[kit->second].m_spent || m_transfers[kit->second].amount() >= tx.vout[o].amount) { @@ -375,17 +381,20 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ // The new larger output replaced a previous smaller one tx_money_got_in_outs -= tx.vout[o].amount; - transfer_details &td = m_transfers[kit->second]; - td.m_block_height = height; - td.m_internal_output_index = o; - td.m_global_output_index = res.o_indexes[o]; - td.m_tx = tx; - THROW_WALLET_EXCEPTION_IF(td.m_key_image != ki, error::wallet_internal_error, "Inconsistent key images"); - THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status"); - - LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx)); - if (0 != m_callback) - m_callback->on_money_received(height, td.m_tx, td.m_internal_output_index); + if (!pool) + { + transfer_details &td = m_transfers[kit->second]; + td.m_block_height = height; + td.m_internal_output_index = o; + td.m_global_output_index = res.o_indexes[o]; + td.m_tx = tx; + THROW_WALLET_EXCEPTION_IF(td.m_key_image != ki, error::wallet_internal_error, "Inconsistent key images"); + THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status"); + + LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx)); + if (0 != m_callback) + m_callback->on_money_received(height, td.m_tx, td.m_internal_output_index); + } } } } @@ -462,8 +471,11 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ payment.m_block_height = height; payment.m_unlock_time = tx.unlock_time; payment.m_timestamp = ts; - m_payments.emplace(payment_id, payment); - LOG_PRINT_L2("Payment found: " << payment_id << " / " << payment.m_tx_hash << " / " << payment.m_amount); + if (pool) + m_unconfirmed_payments.emplace(payment_id, payment); + else + m_payments.emplace(payment_id, payment); + LOG_PRINT_L2("Payment found in " << (pool ? "pool" : "block") << ": " << payment_id << " / " << payment.m_tx_hash << " / " << payment.m_amount); } } //---------------------------------------------------------------------------------------------------- @@ -506,7 +518,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry if(b.timestamp + 60*60*24 > m_account.get_createtime() && height >= m_refresh_from_block_height) { TIME_MEASURE_START(miner_tx_handle_time); - process_new_transaction(b.miner_tx, height, b.timestamp, true); + process_new_transaction(b.miner_tx, height, b.timestamp, true, false); TIME_MEASURE_FINISH(miner_tx_handle_time); TIME_MEASURE_START(txs_handle_time); @@ -515,7 +527,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry cryptonote::transaction tx; bool r = parse_and_validate_tx_from_blob(txblob, tx); THROW_WALLET_EXCEPTION_IF(!r, error::tx_parse_error, txblob); - process_new_transaction(tx, height, b.timestamp, false); + process_new_transaction(tx, height, b.timestamp, false, false); } TIME_MEASURE_FINISH(txs_handle_time); LOG_PRINT_L2("Processed block: " << bl_id << ", height " << height << ", " << miner_tx_handle_time + txs_handle_time << "(" << miner_tx_handle_time << "/" << txs_handle_time <<")ms"); @@ -743,20 +755,16 @@ void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_hei } } //---------------------------------------------------------------------------------------------------- -void wallet2::check_pending_txes() +void wallet2::update_pool_state() { - // if we don't have any pending txes, we don't need to check anything - if (m_unconfirmed_txs.empty()) - return; - - // we have at least one pending tx, so get the pool state + // get the pool state cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL::request req; cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL::response res; m_daemon_rpc_mutex.lock(); bool r = epee::net_utils::invoke_http_json_remote_command2(m_daemon_address + "/get_transaction_pool", req, res, m_http_client, 200000); m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "check_pending_txes"); - THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "is_key_image_spent"); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_transaction_pool"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_transaction_pool"); THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_tx_pool_error); // remove any pending tx that's not in the pool @@ -793,6 +801,120 @@ void wallet2::check_pending_txes() } } } + + // remove pool txes to us that aren't in the pool anymore + std::unordered_map<crypto::hash, wallet2::payment_details>::iterator uit = m_unconfirmed_payments.begin(); + while (uit != m_unconfirmed_payments.end()) + { + const std::string txid = string_tools::pod_to_hex(uit->first); + bool found = false; + for (auto it2: res.transactions) + { + if (it2.id_hash == txid) + { + found = true; + break; + } + } + auto pit = uit++; + if (!found) + { + m_unconfirmed_payments.erase(pit); + } + } + + // add new pool txes to us + for (auto it: res.transactions) + { + cryptonote::blobdata txid_data; + if(epee::string_tools::parse_hexstr_to_binbuff(it.id_hash, txid_data)) + { + const crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + if (m_unconfirmed_payments.find(txid) == m_unconfirmed_payments.end()) + { + LOG_PRINT_L1("Found new pool tx: " << txid); + bool found = false; + for (const auto &i: m_unconfirmed_txs) + { + if (i.first == txid) + { + found = true; + break; + } + } + if (!found) + { + // not one of those we sent ourselves + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; + req.txs_hashes.push_back(it.id_hash); + req.decode_as_json = false; + m_daemon_rpc_mutex.lock(); + bool r = epee::net_utils::invoke_http_json_remote_command2(m_daemon_address + "/gettransactions", req, res, m_http_client, 200000); + m_daemon_rpc_mutex.unlock(); + if (r && res.status == CORE_RPC_STATUS_OK) + { + if (res.txs.size() == 1) + { + // might have just been put in a block + if (res.txs[0].in_pool) + { + cryptonote::transaction tx; + cryptonote::blobdata bd; + crypto::hash tx_hash, tx_prefix_hash; + if (epee::string_tools::parse_hexstr_to_binbuff(res.txs[0].as_hex, bd)) + { + if (cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash)) + { + if (tx_hash == txid) + { + process_new_transaction(tx, 0, time(NULL), false, true); + } + else + { + LOG_PRINT_L0("Mismatched txids when processing unconfimed txes from pool"); + } + } + else + { + LOG_PRINT_L0("failed to validate transaction from daemon"); + } + } + else + { + LOG_PRINT_L0("Failed to parse tx " << txid); + } + } + else + { + LOG_PRINT_L1("Tx " << txid << " was in pool, but is no more"); + } + } + else + { + LOG_PRINT_L0("Expected 1 tx, got " << res.txs.size()); + } + } + else + { + LOG_PRINT_L0("Error calling gettransactions daemon RPC: r " << r << ", status " << res.status); + } + } + else + { + LOG_PRINT_L1("We sent that one"); + } + } + else + { + LOG_PRINT_L1("Already saw that one"); + } + } + else + { + LOG_PRINT_L0("Failed to parse txid"); + } + } } //---------------------------------------------------------------------------------------------------- void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history) @@ -931,7 +1053,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re try { - check_pending_txes(); + update_pool_state(); } catch (...) { @@ -1733,6 +1855,13 @@ void wallet2::get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wall } } //---------------------------------------------------------------------------------------------------- +void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments) const +{ + for (auto i = m_unconfirmed_payments.begin(); i != m_unconfirmed_payments.end(); ++i) { + unconfirmed_payments.push_back(*i); + } +} +//---------------------------------------------------------------------------------------------------- void wallet2::rescan_spent() { std::vector<std::string> key_images; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index d004b7da9..de3580777 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -303,6 +303,8 @@ namespace tools void get_payments_out(std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>>& confirmed_payments, uint64_t min_height, uint64_t max_height = (uint64_t)-1) const; void get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wallet2::unconfirmed_transfer_details>>& unconfirmed_payments) const; + void get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments) const; + uint64_t get_blockchain_current_height() const { return m_local_bc_height; } void rescan_spent(); void rescan_blockchain(bool refresh = true); @@ -334,6 +336,9 @@ namespace tools if(ver < 12) return; a & m_tx_notes; + if(ver < 13) + return; + a & m_unconfirmed_payments; } /*! @@ -389,6 +394,8 @@ namespace tools std::string sign(const std::string &data) const; bool verify(const std::string &data, const cryptonote::account_public_address &address, const std::string &signature) const; + void update_pool_state(); + private: /*! * \brief Stores wallet information to wallet file. @@ -404,7 +411,7 @@ namespace tools * \param password Password of wallet file */ bool load_keys(const std::string& keys_file_name, const std::string& password); - void process_new_transaction(const cryptonote::transaction& tx, uint64_t height, uint64_t ts, bool miner_tx); + void process_new_transaction(const cryptonote::transaction& tx, uint64_t height, uint64_t ts, bool miner_tx, bool pool); void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height); void detach_blockchain(uint64_t height); void get_short_chain_history(std::list<crypto::hash>& ids) const; @@ -428,7 +435,6 @@ namespace tools void check_acc_out(const cryptonote::account_keys &acc, const cryptonote::tx_out &o, const crypto::public_key &tx_pub_key, size_t i, uint64_t &money_transfered, bool &error) const; void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const; uint64_t get_upper_tranaction_size_limit(); - void check_pending_txes(); std::vector<uint64_t> get_unspent_amounts_vector(); uint64_t sanitize_fee_multiplier(uint64_t fee_multiplier) const; @@ -441,6 +447,7 @@ namespace tools std::atomic<uint64_t> m_local_bc_height; //temporary workaround std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs; std::unordered_map<crypto::hash, confirmed_transfer_details> m_confirmed_txs; + std::unordered_map<crypto::hash, payment_details> m_unconfirmed_payments; std::unordered_map<crypto::hash, crypto::secret_key> m_tx_keys; transfer_container m_transfers; @@ -469,7 +476,7 @@ namespace tools uint64_t m_refresh_from_block_height; }; } -BOOST_CLASS_VERSION(tools::wallet2, 12) +BOOST_CLASS_VERSION(tools::wallet2, 13) BOOST_CLASS_VERSION(tools::wallet2::payment_details, 1) BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 3) BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 2) diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h index 66987e4c5..2c5836573 100644 --- a/src/wallet/wallet2_api.h +++ b/src/wallet/wallet2_api.h @@ -50,6 +50,14 @@ struct PendingTransaction Status_Ok, Status_Error }; + + enum Priority { + Priority_Low = 1, + Priority_Medium = 2, + Priority_High = 3, + Priority_Last + }; + virtual ~PendingTransaction() = 0; virtual int status() const = 0; virtual std::string errorString() const = 0; @@ -108,7 +116,10 @@ struct WalletListener virtual ~WalletListener() = 0; virtual void moneySpent(const std::string &txId, uint64_t amount) = 0; virtual void moneyReceived(const std::string &txId, uint64_t amount) = 0; - // TODO: on_skip_transaction; + // generic callback, called when any event (sent/received/block reveived/etc) happened with the wallet; + virtual void updated() = 0; + // called when wallet refreshed by background thread or explicitly called be calling "refresh" synchronously + virtual void refreshed() = 0; }; @@ -163,9 +174,38 @@ struct Wallet * \return */ virtual std::string keysFilename() const = 0; - + /*! + * \brief init - initializes wallet with daemon connection params. implicitly connects to the daemon + * and refreshes the wallet. "refreshed" callback will be invoked. if daemon_address is + * local address, "trusted daemon" will be set to true forcibly + * + * \param daemon_address - daemon address in "hostname:port" format + * \param upper_transaction_size_limit + * \return - true if initialized and refreshed successfully + */ virtual bool init(const std::string &daemon_address, uint64_t upper_transaction_size_limit) = 0; + + /*! + * \brief init - initalizes wallet asynchronously. logic is the same as "init" but returns immediately. + * "refreshed" callback will be invoked. + * + * \param daemon_address - daemon address in "hostname:port" format + * \param upper_transaction_size_limit + * \return - true if initialized and refreshed successfully + */ + virtual void initAsync(const std::string &daemon_address, uint64_t upper_transaction_size_limit) = 0; + + /** + * @brief connectToDaemon - connects to the daemon. TODO: check if it can be removed + * @return + */ virtual bool connectToDaemon() = 0; + + /** + * @brief connected - checks if the wallet connected to the daemon + * @return - true if connected + */ + virtual bool connected() const = 0; virtual void setTrustedDaemon(bool arg) = 0; virtual bool trustedDaemon() const = 0; virtual uint64_t balance() const = 0; @@ -175,23 +215,36 @@ struct Wallet static uint64_t amountFromString(const std::string &amount); static uint64_t amountFromDouble(double amount); static std::string genPaymentId(); + static bool paymentIdValid(const std::string &paiment_id); - // TODO? - // virtual uint64_t unlockedDustBalance() const = 0; + /** + * @brief refresh - refreshes the wallet, updating transactions from daemon + * @return - true if refreshed successfully; + */ virtual bool refresh() = 0; + /** + * @brief refreshAsync - refreshes wallet asynchronously. + */ + virtual void refreshAsync() = 0; /*! * \brief createTransaction creates transaction. if dst_addr is an integrated address, payment_id is ignored * \param dst_addr destination address as string * \param payment_id optional payment_id, can be empty string * \param amount amount * \param mixin_count mixin count. if 0 passed, wallet will use default value + * \param priority * \return PendingTransaction object. caller is responsible to check PendingTransaction::status() * after object returned */ virtual PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, - uint64_t amount, uint32_t mixin_count) = 0; + uint64_t amount, uint32_t mixin_count, + PendingTransaction::Priority = PendingTransaction::Priority_Low) = 0; + /*! + * \brief disposeTransaction - destroys transaction object + * \param t - pointer to the "PendingTransaction" object. Pointer is not valid after function returned; + */ virtual void disposeTransaction(PendingTransaction * t) = 0; virtual TransactionHistory * history() const = 0; virtual void setListener(WalletListener *) = 0; @@ -271,8 +324,22 @@ struct WalletManager struct WalletManagerFactory { + // logging levels for underlying library + enum LogLevel { + LogLevel_Silent = -1, + LogLevel_0 = 0, + LogLevel_1 = 1, + LogLevel_2 = 2, + LogLevel_3 = 3, + LogLevel_4 = 4, + LogLevel_Min = LogLevel_Silent, + LogLevel_Max = LogLevel_4 + }; + static WalletManager * getWalletManager(); + static void setLogLevel(int level); }; + } diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 5c42e1c53..dbfb880a1 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -918,8 +918,8 @@ namespace tools std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>> payments; m_wallet.get_payments_out(payments, min_height, max_height); for (std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { - res.in.push_back(wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry()); - wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry &entry = res.in.back(); + res.out.push_back(wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry()); + wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry &entry = res.out.back(); const tools::wallet2::confirmed_transfer_details &pd = i->second; entry.txid = string_tools::pod_to_hex(i->first); @@ -969,6 +969,29 @@ namespace tools } } + if (req.pool) + { + m_wallet.update_pool_state(); + + std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments; + m_wallet.get_unconfirmed_payments(payments); + for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { + res.pool.push_back(wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry()); + wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry &entry = res.pool.back(); + const tools::wallet2::payment_details &pd = i->second; + + entry.txid = string_tools::pod_to_hex(pd.m_tx_hash); + entry.payment_id = string_tools::pod_to_hex(i->first); + if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos) + entry.payment_id = entry.payment_id.substr(0,16); + entry.height = 0; + entry.timestamp = pd.m_timestamp; + entry.amount = pd.m_amount; + entry.fee = 0; // TODO + entry.note = m_wallet.get_tx_note(pd.m_tx_hash); + } + } + return true; } //------------------------------------------------------------------------------------------------------------------------------ diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 1404340da..f4eefcd1a 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -496,6 +496,7 @@ namespace wallet_rpc bool out; bool pending; bool failed; + bool pool; bool filter_by_height; uint64_t min_height; @@ -506,6 +507,7 @@ namespace wallet_rpc KV_SERIALIZE(out); KV_SERIALIZE(pending); KV_SERIALIZE(failed); + KV_SERIALIZE(pool); KV_SERIALIZE(filter_by_height); KV_SERIALIZE(min_height); KV_SERIALIZE(max_height); @@ -541,12 +543,14 @@ namespace wallet_rpc std::list<entry> out; std::list<entry> pending; std::list<entry> failed; + std::list<entry> pool; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(in); KV_SERIALIZE(out); KV_SERIALIZE(pending); KV_SERIALIZE(failed); + KV_SERIALIZE(pool); END_KV_SERIALIZE_MAP() }; }; |