diff --git a/src/utils/monero_utils.cpp b/src/utils/monero_utils.cpp index 4545bf2..198cbd2 100644 --- a/src/utils/monero_utils.cpp +++ b/src/utils/monero_utils.cpp @@ -317,8 +317,6 @@ void monero_utils::binary_blocks_to_json(const std::string &bin, std::string &js } monero_tx_set monero_utils::deserialize_tx_set(const string& tx_set_str) { - cout << "monero_utils::deserialize_tx_set(...)" << endl; - cout << tx_set_str; // deserialize tx set to property std::istringstream iss = tx_set_str.empty() ? std::istringstream() : std::istringstream(tx_set_str); diff --git a/src/wallet/monero_wallet.cpp b/src/wallet/monero_wallet.cpp index 4634284..372736b 100644 --- a/src/wallet/monero_wallet.cpp +++ b/src/wallet/monero_wallet.cpp @@ -1958,6 +1958,7 @@ namespace monero { out_transfer->m_amount = *tx_amounts_iter; // init other known fields + tx->m_is_outgoing = true; tx->m_payment_id = request.m_payment_id; tx->m_is_confirmed = false; tx->m_is_miner_tx = false; @@ -2369,7 +2370,162 @@ namespace monero { } monero_tx_set monero_wallet::parse_tx_set(const monero_tx_set& tx_set) { - throw runtime_error("Not implemented"); + + // get unsigned and multisig tx sets + string unsigned_tx_hex = tx_set.m_unsigned_tx_hex == boost::none ? "" : tx_set.m_unsigned_tx_hex.get(); + string multisig_tx_hex = tx_set.m_multisig_tx_hex == boost::none ? "" : tx_set.m_multisig_tx_hex.get(); + + // validate request + if (m_w2->key_on_device()) throw runtime_error("command not supported by HW wallet"); + if (m_w2->watch_only()) throw runtime_error("command not supported by watch-only wallet"); + if (unsigned_tx_hex.empty() && multisig_tx_hex.empty()) throw runtime_error("no txset provided"); + + std::vector tx_constructions; + if (!unsigned_tx_hex.empty()) { + try { + tools::wallet2::unsigned_tx_set exported_txs; + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(unsigned_tx_hex, blob)) throw runtime_error("Failed to parse hex."); + if (!m_w2->parse_unsigned_tx_from_str(blob, exported_txs)) throw runtime_error("cannot load unsigned_txset"); + tx_constructions = exported_txs.txes; + } + catch (const std::exception &e) { + throw runtime_error("failed to parse unsigned transfers: " + std::string(e.what())); + } + } else if (!multisig_tx_hex.empty()) { + try { + tools::wallet2::multisig_tx_set exported_txs; + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(multisig_tx_hex, blob)) throw runtime_error("Failed to parse hex."); + if (!m_w2->parse_multisig_tx_from_str(blob, exported_txs)) throw runtime_error("cannot load multisig_txset"); + for (size_t n = 0; n < exported_txs.m_ptx.size(); ++n) { + tx_constructions.push_back(exported_txs.m_ptx[n].construction_data); + } + } + catch (const std::exception &e) { + throw runtime_error("failed to parse multisig transfers: " + std::string(e.what())); + } + } + + std::vector ptx; // TODO wallet_rpc_server: unused variable + try + { + + // gather info for each tx + vector> txs; + std::unordered_map> dests; + int first_known_non_zero_change_index = -1; + for (size_t n = 0; n < tx_constructions.size(); ++n) + { + // pre-initialize tx + shared_ptr tx = make_shared(); + tx->m_is_outgoing = true; + tx->m_input_sum = 0; + tx->m_output_sum = 0; + tx->m_change_amount = 0; + tx->m_num_dummy_outputs = 0; + tx->m_mixin = std::numeric_limits::max() - 1; // smaller ring sizes will overwrite // TODO: remove - 1 when switched to ring size + + const tools::wallet2::tx_construction_data &cd = tx_constructions[n]; + std::vector tx_extra_fields; + bool has_encrypted_payment_id = false; + crypto::hash8 payment_id8 = crypto::null_hash8; + if (cryptonote::parse_tx_extra(cd.extra, tx_extra_fields)) + { + cryptonote::tx_extra_nonce extra_nonce; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + crypto::hash payment_id; + if(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) + { + if (payment_id8 != crypto::null_hash8) + { + tx->m_payment_id = epee::string_tools::pod_to_hex(payment_id8); + has_encrypted_payment_id = true; + } + } + else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) + { + tx->m_payment_id = epee::string_tools::pod_to_hex(payment_id); + } + } + } + + for (size_t s = 0; s < cd.sources.size(); ++s) + { + tx->m_input_sum = tx->m_input_sum.get() + cd.sources[s].amount; + size_t ring_size = cd.sources[s].outputs.size(); + if (ring_size < tx->m_mixin.get() + 1) + tx->m_mixin = ring_size - 1; // TODO: switch to m_ring_size + } + for (size_t d = 0; d < cd.splitted_dsts.size(); ++d) + { + const cryptonote::tx_destination_entry &entry = cd.splitted_dsts[d]; + std::string address = cryptonote::get_account_address_as_str(m_w2->nettype(), entry.is_subaddress, entry.addr); + if (has_encrypted_payment_id && !entry.is_subaddress && address != entry.original) + address = cryptonote::get_account_integrated_address_as_str(m_w2->nettype(), entry.addr, payment_id8); + auto i = dests.find(entry.addr); + if (i == dests.end()) + dests.insert(std::make_pair(entry.addr, std::make_pair(address, entry.amount))); + else + i->second.second += entry.amount; + tx->m_output_sum = tx->m_output_sum.get() + entry.amount; + } + if (cd.change_dts.amount > 0) + { + auto it = dests.find(cd.change_dts.addr); + if (it == dests.end()) throw runtime_error("Claimed change does not go to a paid address"); + if (it->second.second < cd.change_dts.amount) throw runtime_error("Claimed change is larger than payment to the change address"); + if (cd.change_dts.amount > 0) + { + if (first_known_non_zero_change_index == -1) + first_known_non_zero_change_index = n; + const tools::wallet2::tx_construction_data &cdn = tx_constructions[first_known_non_zero_change_index]; + if (memcmp(&cd.change_dts.addr, &cdn.change_dts.addr, sizeof(cd.change_dts.addr))) throw runtime_error("Change goes to more than one address"); + } + tx->m_change_amount = tx->m_change_amount.get() + cd.change_dts.amount; + it->second.second -= cd.change_dts.amount; + if (it->second.second == 0) + dests.erase(cd.change_dts.addr); + } + + tx->m_outgoing_transfer = make_shared(); + size_t n_dummy_outputs = 0; + for (auto i = dests.begin(); i != dests.end(); ) + { + if (i->second.second > 0) + { + shared_ptr destination = make_shared(); + destination->m_address = i->second.first; + destination->m_amount = i->second.second; + tx->m_outgoing_transfer.get()->m_destinations.push_back(destination); + } + else + tx->m_num_dummy_outputs = tx->m_num_dummy_outputs.get() + 1; + ++i; + } + + if (tx->m_change_amount.get() > 0) + { + const tools::wallet2::tx_construction_data &cd0 = tx_constructions[0]; + tx->m_change_address = get_account_address_as_str(m_w2->nettype(), cd0.subaddr_account > 0, cd0.change_dts.addr); + } + + tx->m_fee = tx->m_input_sum.get() - tx->m_output_sum.get(); + tx->m_unlock_time = cd.unlock_time; + tx->m_extra_hex = epee::to_hex::string({cd.extra.data(), cd.extra.size()}); + txs.push_back(tx); + } + + // build and return tx set + monero_tx_set tx_set; + tx_set.m_txs = txs; + return tx_set; + } + catch (const std::exception &e) + { + throw runtime_error("failed to parse unsigned transfers"); + } } string monero_wallet::sign(const string& msg) const { diff --git a/src/wallet/monero_wallet_model.cpp b/src/wallet/monero_wallet_model.cpp index 69f89d5..6077376 100644 --- a/src/wallet/monero_wallet_model.cpp +++ b/src/wallet/monero_wallet_model.cpp @@ -119,6 +119,9 @@ namespace monero { monero_tx::copy(static_pointer_cast(src), static_pointer_cast(tgt)); // copy wallet extensions + tgt->m_tx_set = src->m_tx_set; + tgt->m_is_incoming = src->m_is_incoming; + tgt->m_is_outgoing = src->m_is_outgoing; if (!src->m_incoming_transfers.empty()) { tgt->m_incoming_transfers = vector>(); for (const shared_ptr& transfer : src->m_incoming_transfers) { @@ -134,27 +137,33 @@ namespace monero { } tgt->m_note = src->m_note; tgt->m_is_unlocked = src->m_is_unlocked; + tgt->m_input_sum = src->m_input_sum; + tgt->m_output_sum = src->m_output_sum; + tgt->m_change_address = src->m_change_address; + tgt->m_change_amount = src->m_change_amount; + tgt->m_num_dummy_outputs = src->m_num_dummy_outputs; + tgt->m_extra_hex = src->m_extra_hex; return tgt; }; boost::property_tree::ptree monero_tx_wallet::to_property_tree() const { boost::property_tree::ptree node = monero_tx::to_property_tree(); + if (m_is_incoming != boost::none) node.put("isIncoming", *m_is_incoming); + if (m_is_outgoing != boost::none) node.put("isOutgoing", *m_is_outgoing); if (!m_incoming_transfers.empty()) node.add_child("incomingTransfers", monero_utils::to_property_tree(m_incoming_transfers)); if (m_outgoing_transfer != boost::none) node.add_child("outgoingTransfer", (*m_outgoing_transfer)->to_property_tree()); if (m_note != boost::none) node.put("note", *m_note); if (m_is_unlocked != boost::none) node.put("isUnlocked", *m_is_unlocked); + if (m_input_sum != boost::none) node.put("inputSum", *m_input_sum); + if (m_output_sum != boost::none) node.put("outputSum", *m_output_sum); + if (m_change_address != boost::none) node.put("changeAddress", *m_change_address); + if (m_change_amount != boost::none) node.put("changeAmount", *m_change_amount); + if (m_num_dummy_outputs != boost::none) node.put("numDummyOutputs", *m_num_dummy_outputs); + if (m_extra_hex != boost::none) node.put("extraHex", *m_extra_hex); return node; } - bool monero_tx_wallet::is_outgoing() const { - return m_outgoing_transfer != boost::none; - } - - bool monero_tx_wallet::is_incoming() const { - return !m_incoming_transfers.empty(); - } - void monero_tx_wallet::merge(const shared_ptr& self, const shared_ptr& other) { merge(static_pointer_cast(self), static_pointer_cast(other)); } @@ -166,9 +175,6 @@ namespace monero { // merge base classes monero_tx::merge(self, other); - // merge wallet extensions - m_note = gen_utils::reconcile(m_note, other->m_note); - // merge incoming transfers if (!other->m_incoming_transfers.empty()) { for (const shared_ptr& transfer : other->m_incoming_transfers) { // NOTE: not using reference so shared_ptr is not deleted when tx is dereferenced @@ -183,6 +189,16 @@ namespace monero { if (self->m_outgoing_transfer == boost::none) self->m_outgoing_transfer = other->m_outgoing_transfer; else self->m_outgoing_transfer.get()->merge(self->m_outgoing_transfer.get(), other->m_outgoing_transfer.get()); } + + // merge simple extensions + m_note = gen_utils::reconcile(m_note, other->m_note); + m_is_unlocked = gen_utils::reconcile(m_is_unlocked, other->m_is_unlocked); + m_input_sum = gen_utils::reconcile(m_input_sum, other->m_input_sum); + m_output_sum = gen_utils::reconcile(m_output_sum, other->m_output_sum); + m_change_address = gen_utils::reconcile(m_change_address, other->m_change_address); + m_change_amount = gen_utils::reconcile(m_change_amount, other->m_change_amount); + m_num_dummy_outputs = gen_utils::reconcile(m_num_dummy_outputs, other->m_num_dummy_outputs); + m_extra_hex = gen_utils::reconcile(m_extra_hex, other->m_extra_hex); } // -------------------------- MONERO TX REQUEST ----------------------------- @@ -281,10 +297,10 @@ namespace monero { } // filter on incoming - if (m_is_incoming != boost::none && m_is_incoming != tx->is_incoming()) return false; + if (m_is_incoming != boost::none && m_is_incoming != tx->m_is_incoming) return false; // filter on outgoing - if (m_is_outgoing != boost::none && m_is_outgoing != tx->is_outgoing()) return false; + if (m_is_outgoing != boost::none && m_is_outgoing != tx->m_is_outgoing) return false; // filter on remaining fields boost::optional txHeight = tx->get_height(); diff --git a/src/wallet/monero_wallet_model.h b/src/wallet/monero_wallet_model.h index 0446524..5169a63 100644 --- a/src/wallet/monero_wallet_model.h +++ b/src/wallet/monero_wallet_model.h @@ -258,15 +258,21 @@ namespace monero { */ struct monero_tx_wallet : public monero_tx { boost::optional> m_tx_set; + boost::optional m_is_incoming; + boost::optional m_is_outgoing; vector> m_incoming_transfers; boost::optional> m_outgoing_transfer; boost::optional m_note; boost::optional m_is_unlocked; + boost::optional m_input_sum; + boost::optional m_output_sum; + boost::optional m_change_address; + boost::optional m_change_amount; + boost::optional m_num_dummy_outputs; + boost::optional m_extra_hex; shared_ptr copy(const shared_ptr& src, const shared_ptr& tgt) const; shared_ptr copy(const shared_ptr& src, const shared_ptr& tgt) const; - bool is_incoming() const; - bool is_outgoing() const; boost::property_tree::ptree to_property_tree() const; void merge(const shared_ptr& self, const shared_ptr& other); void merge(const shared_ptr& self, const shared_ptr& other);