support notifications of unlocked outputs

outputs merged by key images xor appended: pre-ringct indices not unique
This commit is contained in:
woodser
2020-07-25 11:00:32 -04:00
parent 5644876fe3
commit 68297fdbcd
3 changed files with 117 additions and 77 deletions
+30 -48
View File
@@ -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<monero_output>& merger : other->m_outputs) {
// determine if key images present
int numKeyImages = 0;
for (const std::shared_ptr<monero_output> 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<monero_output>& 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<monero_output>& merger : other->m_outputs) {
if (numKeyImages > 0) {
bool merged = false;
merger->m_tx = self;
for (const std::shared_ptr<monero_output>& mergee : this->m_outputs) {
if (mergee->m_index.get() == merger->m_index.get()) {
for (const std::shared_ptr<monero_output>& 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<monero_output> 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<monero_output>& 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<monero_output>& merger : other->m_outputs) {
bool merged = false;
merger->m_tx = self;
for (const std::shared_ptr<monero_output>& 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<monero_key_image>& self, const std::shared_ptr<monero_key_image>& 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 -----------------------------
+87 -25
View File
@@ -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<monero_tx_wallet> tx = std::static_pointer_cast<monero_tx_wallet>(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<monero_output_wallet> output = std::make_shared<monero_output_wallet>();
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<monero_output_wallet> output = std::make_shared<monero_output_wallet>();
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<uint64_t> m_sync_start_height;
boost::optional<uint64_t> m_sync_end_height;
uint64_t m_prev_balance;
uint64_t m_prev_unlocked_balance;
std::vector<std::shared_ptr<monero_tx_wallet>> 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<std::shared_ptr<monero_tx_wallet>> locked_txs = m_wallet.get_txs(query);
// collect hashes of txs no longer locked
std::vector<std::string> tx_hashes_no_longer_locked;
for (const std::shared_ptr<monero_tx_wallet>& prev_locked_tx : m_locked_txs) {
bool found = false;
for (const std::shared_ptr<monero_tx_wallet>& 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<std::shared_ptr<monero_tx_wallet>> 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<std::string> 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<monero_tx_wallet>& unlocked_tx : txs_no_longer_locked) {
for (const std::shared_ptr<monero_output_wallet>& 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<wallet2_listener>(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<std::shared_ptr<monero_transfer>> 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;
}
}
-4
View File
@@ -288,10 +288,6 @@ namespace monero {
std::unique_ptr<wallet2_listener> m_w2_listener; // internal wallet implementation listener
std::set<monero_wallet_listener*> 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<monero_subaddress> get_subaddresses_aux(uint32_t account_idx, const std::vector<uint32_t>& subaddress_indices, const std::vector<tools::wallet2::transfer_details>& transfers) const;
std::vector<std::shared_ptr<monero_transfer>> get_transfers_aux(const monero_transfer_query& query) const;