diff --git a/src/daemon/monero_daemon_model.cpp b/src/daemon/monero_daemon_model.cpp index a1eff37..3baf5e0 100644 --- a/src/daemon/monero_daemon_model.cpp +++ b/src/daemon/monero_daemon_model.cpp @@ -427,6 +427,7 @@ namespace monero { m_fee = gen_utils::reconcile(m_fee, other->m_fee, "tx fee"); m_ring_size = gen_utils::reconcile(m_ring_size, other->m_ring_size, "tx m_ring_size"); m_is_confirmed = gen_utils::reconcile(m_is_confirmed, other->m_is_confirmed); + m_is_miner_tx = gen_utils::reconcile(m_is_miner_tx, other->m_is_miner_tx); m_relay = gen_utils::reconcile(m_relay, other->m_relay); m_is_relayed = gen_utils::reconcile(m_is_relayed, other->m_is_relayed); m_is_double_spend_seen = gen_utils::reconcile(m_is_double_spend_seen, other->m_is_double_spend_seen); @@ -483,60 +484,37 @@ namespace monero { throw std::runtime_error("Some outputs have an output index and some do not"); } - // merge by output indices if present - if (num_indices > 0) { - for (const std::shared_ptr& merger : other->m_outputs) { + // determine if key images present + int numKeyImages = 0; + for (const std::shared_ptr output : m_outputs) { + if (output->m_key_image != boost::none) { + if ((*output->m_key_image)->m_hex == boost::none) throw std::runtime_error("Key image hex cannot be null"); + numKeyImages++; + } + } + for (const std::shared_ptr& output : other->m_outputs) { + if (output->m_key_image != boost::none) { + if ((*output->m_key_image)->m_hex == boost::none) throw std::runtime_error("Key image hex cannot be null"); + numKeyImages++; + } + } + if (numKeyImages != 0 && m_outputs.size() + other->m_outputs.size() != numKeyImages) throw std::runtime_error("Some outputs have a key image and some do not"); + + // merge outputs by key image if present, otherwise append (cannot merge by global index because pre-RingCT output indices are not unique) + for (const std::shared_ptr& merger : other->m_outputs) { + if (numKeyImages > 0) { bool merged = false; merger->m_tx = self; - for (const std::shared_ptr& mergee : this->m_outputs) { - if (mergee->m_index.get() == merger->m_index.get()) { + for (const std::shared_ptr& mergee : m_outputs) { + if ((*mergee->m_key_image)->m_hex == (*merger->m_key_image)->m_hex) { mergee->merge(mergee, merger); merged = true; break; } } - if (!merged) this->m_outputs.push_back(merger); - } - } else { - - // determine if key images present - int numKeyImages = 0; - for (const std::shared_ptr output : m_outputs) { - if (output->m_key_image != boost::none) { - if ((*output->m_key_image)->m_hex == boost::none) throw std::runtime_error("Key image hex cannot be null"); - numKeyImages++; - } - } - for (const std::shared_ptr& output : other->m_outputs) { - if (output->m_key_image != boost::none) { - if ((*output->m_key_image)->m_hex == boost::none) throw std::runtime_error("Key image hex cannot be null"); - numKeyImages++; - } - } - if (numKeyImages != 0 && m_outputs.size() + other->m_outputs.size() != numKeyImages) throw std::runtime_error("Some outputs have a key image and some do not"); - - // merge by key images - if (numKeyImages > 0) { - for (const std::shared_ptr& merger : other->m_outputs) { - bool merged = false; - merger->m_tx = self; - for (const std::shared_ptr& mergee : m_outputs) { - if ((*mergee->m_key_image)->m_hex == (*merger->m_key_image)->m_hex) { - mergee->merge(mergee, merger); - merged = true; - break; - } - } - if (!merged) m_outputs.push_back(merger); - } - } - - // merge by position - else { - if (m_outputs.size() != other->m_outputs.size()) throw std::runtime_error("Vout sizes are different"); - for (int i = 0; i < other->m_outputs.size(); i++) { - m_outputs.at(i)->merge(m_outputs.at(i), other->m_outputs.at(i)); - } + if (!merged) m_outputs.push_back(merger); // add new key image + } else { + m_outputs.push_back(merger); // no key images present, append } } } @@ -611,7 +589,11 @@ namespace monero { } void monero_key_image::merge(const std::shared_ptr& self, const std::shared_ptr& other) { - throw std::runtime_error("Not implemented"); + MTRACE("monero_key_image::merge(self, other)"); + if (this != self.get()) throw std::runtime_error("this != self"); + if (self == other) return; + m_hex = gen_utils::reconcile(m_hex, other->m_hex); + m_signature = gen_utils::reconcile(m_signature, other->m_signature); } // ------------------------------ MONERO OUTPUT ----------------------------- diff --git a/src/wallet/monero_wallet_core.cpp b/src/wallet/monero_wallet_core.cpp index 2f607d2..5e5c714 100644 --- a/src/wallet/monero_wallet_core.cpp +++ b/src/wallet/monero_wallet_core.cpp @@ -451,11 +451,14 @@ namespace monero { // compare by height if (tx_height_less_than(ow1->m_tx, ow2->m_tx)) return true; - // compare by account index, subaddress index, and output + // compare by account index, subaddress index, output index, then key image hex if (ow1->m_account_index.get() < ow2->m_account_index.get()) return true; - else if (ow1->m_account_index.get() == ow2->m_account_index.get()) { + if (ow1->m_account_index.get() == ow2->m_account_index.get()) { if (ow1->m_subaddress_index.get() < ow2->m_subaddress_index.get()) return true; - if (ow1->m_subaddress_index.get() == ow2->m_subaddress_index.get() && ow1->m_index.get() < ow2->m_index.get()) return true; + if (ow1->m_subaddress_index.get() == ow2->m_subaddress_index.get()) { + if (ow1->m_index.get() < ow2->m_index.get()) return true; + if (ow1->m_index.get() == ow2->m_index.get()) return ow1->m_key_image.get()->m_hex.get().compare(ow2->m_key_image.get()->m_hex.get()) < 0; + } } return false; } @@ -689,6 +692,8 @@ namespace monero { wallet2_listener(monero_wallet_core& wallet, tools::wallet2& m_w2) : m_wallet(wallet), m_w2(m_w2) { this->m_sync_start_height = boost::none; this->m_sync_end_height = boost::none; + m_prev_balance = wallet.get_balance(); + m_prev_unlocked_balance = wallet.get_unlocked_balance(); } ~wallet2_listener() { @@ -709,6 +714,7 @@ namespace monero { void on_sync_end() { m_sync_start_height = boost::none; m_sync_end_height = boost::none; + check_for_changed_funds(); } void on_new_block(uint64_t height, const cryptonote::block& cn_block) override { @@ -729,13 +735,9 @@ namespace monero { for (monero_wallet_listener* listener : m_wallet.get_listeners()) { listener->on_sync_progress(height, *m_sync_start_height, *m_sync_end_height, percent_done, message); } - } - void on_balances_changed(uint64_t new_balance, uint64_t new_unlocked_balance) { - if (m_wallet.get_listeners().empty()) return; - for (monero_wallet_listener* listener : m_wallet.get_listeners()) { - listener->on_balances_changed(new_balance, new_unlocked_balance); - } + // notify if balances or unlocked txs changed + check_for_changed_funds(); } void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& cn_tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) override { @@ -744,6 +746,7 @@ namespace monero { // create library tx std::shared_ptr tx = std::static_pointer_cast(monero_utils::cn_tx_to_tx(cn_tx, true)); tx->m_hash = epee::string_tools::pod_to_hex(txid); + tx->m_is_locked = true; std::shared_ptr output = std::make_shared(); tx->m_outputs.push_back(output); output->m_tx = tx; @@ -756,8 +759,8 @@ namespace monero { listener->on_output_received(*output); } - // notify if balances changed - m_wallet.check_for_changed_balances(); + // notify if balances or unlocked txs changed + check_for_changed_funds(); // free memory output.reset(); @@ -775,6 +778,7 @@ namespace monero { tx->m_block = block; tx->m_hash = epee::string_tools::pod_to_hex(txid); tx->m_unlock_time = unlock_time; + tx->m_is_locked = true; std::shared_ptr output = std::make_shared(); tx->m_outputs.push_back(output); output->m_tx = tx; @@ -788,7 +792,7 @@ namespace monero { } // notify if balances changed - m_wallet.check_for_changed_balances(); + check_for_changed_funds(); // free memory monero_utils::free(block); @@ -820,7 +824,7 @@ namespace monero { } // notify if balances changed - m_wallet.check_for_changed_balances(); + check_for_changed_funds(); // free memory monero_utils::free(block); @@ -833,6 +837,73 @@ namespace monero { tools::wallet2& m_w2; // internal wallet implementation to listen to boost::optional m_sync_start_height; boost::optional m_sync_end_height; + uint64_t m_prev_balance; + uint64_t m_prev_unlocked_balance; + std::vector> m_locked_txs; + + void check_for_changed_funds() { + if (m_wallet.get_listeners().empty()) return; // skip if no listeners + if (m_prev_balance != m_wallet.get_balance() || m_prev_unlocked_balance != m_wallet.get_unlocked_balance()) { + on_balances_changed(m_wallet.get_balance(), m_wallet.get_unlocked_balance()); + m_prev_balance = m_wallet.get_balance(); + m_prev_unlocked_balance = m_wallet.get_unlocked_balance(); + } + if (m_wallet.is_synced()) check_for_changed_unlocked_txs(); // check for newly unlocked outputs if synced + } + + void on_balances_changed(uint64_t new_balance, uint64_t new_unlocked_balance) { + if (m_wallet.get_listeners().empty()) return; + for (monero_wallet_listener* listener : m_wallet.get_listeners()) { + listener->on_balances_changed(new_balance, new_unlocked_balance); + } + } + + // TODO: this can probably be optimized using e.g. wallet2.get_num_rct_outputs() or wallet2.get_num_transfer_details(), or by retaining confirmed block height and only checking on or after unlock height, etc + void check_for_changed_unlocked_txs() { + if (m_wallet.get_listeners().empty()) return; // skip if no listeners + + // get locked txs + monero_tx_query query = monero_tx_query(); + query.m_is_locked = true; + query.m_is_confirmed = true; + std::vector> locked_txs = m_wallet.get_txs(query); + + // collect hashes of txs no longer locked + std::vector tx_hashes_no_longer_locked; + for (const std::shared_ptr& prev_locked_tx : m_locked_txs) { + bool found = false; + for (const std::shared_ptr& cur_locked_tx : locked_txs) { + if (cur_locked_tx->m_hash.get() == prev_locked_tx->m_hash.get()) { + found = true; + break; + } + } + if (!found) tx_hashes_no_longer_locked.push_back(prev_locked_tx->m_hash.get()); + } + + // fetch txs that are no longer locked + std::vector> txs_no_longer_locked; + if (!tx_hashes_no_longer_locked.empty()) { + query.m_hashes = tx_hashes_no_longer_locked; + query.m_is_locked = false; + query.m_is_confirmed = true; + query.m_include_outputs = true; // TODO: fetch outputs that are no longer locked directly? + std::vector missing_tx_hashes; + txs_no_longer_locked = m_wallet.get_txs(query, missing_tx_hashes); + } + + // notify listeners of newly unlocked tx outputs + for (const std::shared_ptr& unlocked_tx : txs_no_longer_locked) { + for (const std::shared_ptr& output : unlocked_tx->get_outputs_wallet()) { + for (monero_wallet_listener* listener : m_wallet.get_listeners()) { + listener->on_output_received(*output); + } + } + } + + // re-assign currently locked txs + m_locked_txs = locked_txs; + } }; // --------------------------- STATIC WALLET UTILS -------------------------- @@ -3052,8 +3123,6 @@ namespace monero { MTRACE("monero_wallet_core.cpp init_common()"); m_w2_listener = std::unique_ptr(new wallet2_listener(*this, *m_w2)); if (get_daemon_connection() == boost::none) m_is_connected = false; - m_prev_balance = get_balance(); - m_prev_unlocked_balance = get_unlocked_balance(); m_is_synced = false; m_rescan_on_sync = false; m_syncing_enabled = false; @@ -3071,14 +3140,6 @@ namespace monero { #endif } - void monero_wallet_core::check_for_changed_balances() { - if (m_prev_balance != get_balance() || m_prev_unlocked_balance != get_unlocked_balance()) { - m_prev_balance = get_balance(); - m_prev_unlocked_balance = get_unlocked_balance(); - m_w2_listener->on_balances_changed(m_prev_balance, m_prev_unlocked_balance); - } - } - std::vector> monero_wallet_core::get_transfers_aux(const monero_transfer_query& query) const { MTRACE("monero_wallet_core::get_transfers(query)"); @@ -3325,6 +3386,8 @@ namespace monero { while (m_syncing_enabled) { try { lock_and_sync(); + } catch (std::exception const& e) { + std::cout << "monero_wallet_core failed to background synchronize: " << e.what() << std::endl; } catch (...) { std::cout << "monero_wallet_core failed to background synchronize" << std::endl; } @@ -3385,9 +3448,8 @@ namespace monero { // find and save rings m_w2->find_and_save_rings(false); - // notify listeners of sync end and check for updated balances + // notify listeners of sync end and check for updated funds m_w2_listener->on_sync_end(); - check_for_changed_balances(); return result; } } diff --git a/src/wallet/monero_wallet_core.h b/src/wallet/monero_wallet_core.h index a9f52df..41477ec 100644 --- a/src/wallet/monero_wallet_core.h +++ b/src/wallet/monero_wallet_core.h @@ -288,10 +288,6 @@ namespace monero { std::unique_ptr m_w2_listener; // internal wallet implementation listener std::set m_listeners; // external wallet listeners - uint64_t m_prev_balance; - uint64_t m_prev_unlocked_balance; - void check_for_changed_balances(); - void init_common(); std::vector get_subaddresses_aux(uint32_t account_idx, const std::vector& subaddress_indices, const std::vector& transfers) const; std::vector> get_transfers_aux(const monero_transfer_query& query) const;