diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/wallet2.cpp | 333 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 86 |
2 files changed, 326 insertions, 93 deletions
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index ed4ab93de..2cbee4812 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -74,6 +74,9 @@ using namespace cryptonote; // arbitrary, used to generate different hashes from the same input #define CHACHA8_KEY_TAIL 0x8c +#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\001" +#define SIGNED_TX_PREFIX "Monero signed tx set\001" + #define KILL_IOSERVICE() \ do { \ work.reset(); \ @@ -173,15 +176,17 @@ bool wallet2::is_deprecated() const return is_old_file_format; } //---------------------------------------------------------------------------------------------------- -void wallet2::set_spent(transfer_details &td, uint64_t height) +void wallet2::set_spent(size_t idx, uint64_t height) { + transfer_details &td = m_transfers[idx]; LOG_PRINT_L2("Setting SPENT at " << height << ": ki " << td.m_key_image << ", amount " << print_money(td.m_amount)); td.m_spent = true; td.m_spent_height = height; } //---------------------------------------------------------------------------------------------------- -void wallet2::set_unspent(transfer_details &td) +void wallet2::set_unspent(size_t idx) { + transfer_details &td = m_transfers[idx]; LOG_PRINT_L2("Setting UNSPENT: ki " << td.m_key_image << ", amount " << print_money(td.m_amount)); td.m_spent = false; td.m_spent_height = 0; @@ -501,7 +506,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s td.m_mask = rct::identity(); td.m_rct = false; } - set_unspent(td); + set_unspent(m_transfers.size()-1); m_key_images[td.m_key_image] = m_transfers.size()-1; LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid()); if (0 != m_callback) @@ -580,7 +585,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s amount = td.amount(); LOG_PRINT_L0("Spent money: " << print_money(amount) << ", with tx: " << txid()); tx_money_spent_in_ins += amount; - set_spent(td, height); + set_spent(it->second, height); if (0 != m_callback) m_callback->on_money_spent(height, tx, amount, tx); } @@ -997,12 +1002,13 @@ void wallet2::update_pool_state() if (pit->second.m_tx.vin[vini].type() == typeid(txin_to_key)) { txin_to_key &tx_in_to_key = boost::get<txin_to_key>(pit->second.m_tx.vin[vini]); - for (auto &td: m_transfers) + for (size_t i = 0; i < m_transfers.size(); ++i) { + const transfer_details &td = m_transfers[i]; if (td.m_key_image == tx_in_to_key.k_image) { LOG_PRINT_L1("Resetting spent status for output " << vini << ": " << td.m_key_image); - set_unspent(td); + set_unspent(i); break; } } @@ -1301,7 +1307,7 @@ void wallet2::detach_blockchain(uint64_t height) if (td.m_spent && td.m_spent_height >= height) { LOG_PRINT_L1("Resetting spent status for output " << i << ": " << td.m_key_image); - set_unspent(td); + set_unspent(i); } } @@ -1590,7 +1596,8 @@ bool wallet2::verify_password(const std::string& password) const const cryptonote::account_keys& keys = account_data_check.get_keys(); r = r && verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); - r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); + if(!m_watch_only) + r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); return r; } @@ -2128,13 +2135,13 @@ void wallet2::rescan_spent() if (td.m_spent) { LOG_PRINT_L0("Marking output " << i << "(" << td.m_key_image << ") as unspent, it was marked as spent"); - set_unspent(td); + set_unspent(i); td.m_spent_height = 0; } else { LOG_PRINT_L0("Marking output " << i << "(" << td.m_key_image << ") as spent, it was marked as unspent"); - set_spent(td, td.m_spent_height); + set_spent(i, td.m_spent_height); // unknown height, if this gets reorged, it might still be missed } } @@ -2260,7 +2267,7 @@ float wallet2::get_output_relatedness(const transfer_details &td0, const transfe return 0.0f; } //---------------------------------------------------------------------------------------------------- -size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_indices, const std::list<transfer_container::iterator>& selected_transfers) const +size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_indices, const std::list<size_t>& selected_transfers) const { std::vector<size_t> candidates; float best_relatedness = 1.0f; @@ -2268,9 +2275,9 @@ size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::ve { const transfer_details &candidate = transfers[unused_indices[n]]; float relatedness = 0.0f; - for (const auto &i: selected_transfers) + for (size_t i = 0; i < selected_transfers.size(); ++i) { - float r = get_output_relatedness(candidate, *i); + float r = get_output_relatedness(candidate, transfers[i]); if (r > relatedness) { relatedness = r; @@ -2292,7 +2299,7 @@ size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::ve return pop_index (unused_indices, candidates[idx]); } //---------------------------------------------------------------------------------------------------- -size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::list<transfer_container::iterator>& selected_transfers) const +size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::list<size_t>& selected_transfers) const { return pop_best_value_from(m_transfers, unused_indices, selected_transfers); } @@ -2301,7 +2308,7 @@ size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::l // returns: // direct return: amount of money found // modified reference: selected_transfers, a list of iterators/indices of input sources -uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<transfer_container::iterator>& selected_transfers, bool trusted_daemon) +uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<size_t>& selected_transfers, bool trusted_daemon) { uint64_t found_money = 0; while (found_money < needed_money && !unused_transfers_indices.empty()) @@ -2309,7 +2316,7 @@ uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> un size_t idx = pop_best_value(unused_transfers_indices, selected_transfers); transfer_container::iterator it = m_transfers.begin() + idx; - selected_transfers.push_back(it); + selected_transfers.push_back(idx); found_money += it->amount(); } @@ -2513,8 +2520,8 @@ void wallet2::commit_tx(pending_tx& ptx) { payment_id = get_payment_id(ptx); dests = ptx.dests; - BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers) - amount_in += it->amount(); + BOOST_FOREACH(size_t idx, ptx.selected_transfers) + amount_in += m_transfers[idx].amount(); } add_unconfirmed_tx(ptx.tx, amount_in, dests, payment_id, ptx.change_dts.amount); if (store_tx_info()) @@ -2524,9 +2531,9 @@ void wallet2::commit_tx(pending_tx& ptx) LOG_PRINT_L2("transaction " << txid << " generated ok and sent to daemon, key_images: [" << ptx.key_images << "]"); - BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers) + BOOST_FOREACH(size_t idx, ptx.selected_transfers) { - set_spent(*it, 0); + set_spent(idx, 0); } //fee includes dust if dust policy specified it. @@ -2544,7 +2551,153 @@ void wallet2::commit_tx(std::vector<pending_tx>& ptx_vector) commit_tx(ptx); } } +//---------------------------------------------------------------------------------------------------- +bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename) +{ + LOG_PRINT_L0("saving " << ptx_vector.size() << " transactions"); + unsigned_tx_set txs; + for (auto &tx: ptx_vector) + txs.txes.push_back(tx.construction_data); + std::string s = obj_to_json_str(txs); + if (s.empty()) + return false; + LOG_PRINT_L2("Saving unsigned tx data: " << s); + // save as binary as there's no implementation of loading a json_archive + if (!::serialization::dump_binary(txs, s)) + return false; + return epee::file_io_utils::save_string_to_file(filename, std::string(UNSIGNED_TX_PREFIX) + s); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::function<bool(const unsigned_tx_set&)> accept_func) +{ + std::string s; + boost::system::error_code errcode; + + if (!boost::filesystem::exists(unsigned_filename, errcode)) + { + LOG_PRINT_L0("File " << unsigned_filename << " does not exist: " << errcode); + return false; + } + if (!epee::file_io_utils::load_file_to_string(unsigned_filename.c_str(), s)) + { + LOG_PRINT_L0("Failed to load from " << unsigned_filename); + return false; + } + const size_t magiclen = strlen(UNSIGNED_TX_PREFIX); + if (strncmp(s.c_str(), UNSIGNED_TX_PREFIX, magiclen)) + { + LOG_PRINT_L0("Bad magic from " << unsigned_filename); + return false; + } + unsigned_tx_set exported_txs; + if (!::serialization::parse_binary(std::string(s.c_str() + magiclen, s.size() - magiclen), exported_txs)) + { + LOG_PRINT_L0("Failed to parse data from " << unsigned_filename); + return false; + } + LOG_PRINT_L1("Loaded tx unsigned data from binary: " << exported_txs.txes.size() << " transactions"); + + if (accept_func && !accept_func(exported_txs)) + { + LOG_PRINT_L1("Transactions rejected by callback"); + return false; + } + + // sign the transactions + signed_tx_set signed_txes; + for (size_t n = 0; n < exported_txs.txes.size(); ++n) + { + const tools::wallet2::tx_construction_data &sd = exported_txs.txes[n]; + LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, mixin " << (sd.sources[0].outputs.size()-1)); + signed_txes.ptx.push_back(pending_tx()); + tools::wallet2::pending_tx &ptx = signed_txes.ptx.back(); + crypto::secret_key tx_key; + std::vector<cryptonote::tx_destination_entry> dests = sd.destinations; + if (sd.change_dts.amount > 0) + dests.push_back(sd.change_dts); + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sd.sources, dests, sd.extra, ptx.tx, sd.unlock_time, tx_key, sd.use_rct); + THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.destinations, sd.unlock_time, m_testnet); + // we don't test tx size, because we don't know the current limit, due to not having a blockchain, + // and it's a bit pointless to fail there anyway, since it'd be a (good) guess only. We sign anyway, + // and if we really go over limit, the daemon will reject when it gets submitted. Chances are it's + // OK anyway since it was generated in the first place, and rerolling should be within a few bytes. + + // normally, the tx keys are saved in commit_tx, when the tx is actually sent to the daemon. + // we can't do that here since the tx will be sent from the compromised wallet, which we don't want + // to see that info, so we save it here + if (store_tx_info()) + { + const crypto::hash txid = get_transaction_hash(ptx.tx); + m_tx_keys.insert(std::make_pair(txid, tx_key)); + } + + std::string key_images; + bool all_are_txin_to_key = std::all_of(ptx.tx.vin.begin(), ptx.tx.vin.end(), [&](const txin_v& s_e) -> bool + { + CHECKED_GET_SPECIFIC_VARIANT(s_e, const txin_to_key, in, false); + key_images += boost::to_string(in.k_image) + " "; + return true; + }); + THROW_WALLET_EXCEPTION_IF(!all_are_txin_to_key, error::unexpected_txin_type, ptx.tx); + + ptx.key_images = key_images; + ptx.fee = 0; + for (const auto &i: sd.sources) ptx.fee += i.amount; + for (const auto &i: dests) ptx.fee -= i.amount; + ptx.dust = 0; + ptx.dust_added_to_fee = false; + ptx.change_dts = sd.change_dts; +// ptx.selected_transfers = selected_transfers; + ptx.tx_key = rct::rct2sk(rct::identity()); // don't send it back to the untrusted view wallet + ptx.dests = sd.destinations; + ptx.construction_data = sd; + } + + s = obj_to_json_str(signed_txes); + if (s.empty()) + return false; + LOG_PRINT_L2("Saving signed tx data: " << s); + // save as binary as there's no implementation of loading a json_archive + if (!::serialization::dump_binary(signed_txes, s)) + return false; + return epee::file_io_utils::save_string_to_file(signed_filename, std::string(SIGNED_TX_PREFIX) + s); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx) +{ + std::string s; + boost::system::error_code errcode; + signed_tx_set signed_txs; + + if (!boost::filesystem::exists(signed_filename, errcode)) + { + LOG_PRINT_L0("File " << signed_filename << " does not exist: " << errcode); + return false; + } + + if (!epee::file_io_utils::load_file_to_string(signed_filename.c_str(), s)) + { + LOG_PRINT_L0("Failed to load from " << signed_filename); + return false; + } + const size_t magiclen = strlen(SIGNED_TX_PREFIX); + if (strncmp(s.c_str(), SIGNED_TX_PREFIX, magiclen)) + { + LOG_PRINT_L0("Bad magic from " << signed_filename); + return false; + } + if (!::serialization::parse_binary(std::string(s.c_str() + magiclen, s.size() - magiclen), signed_txs)) + { + LOG_PRINT_L0("Failed to parse data from " << signed_filename); + return false; + } + LOG_PRINT_L1("Loaded signed tx data from binary: " << signed_txs.ptx.size() << " transactions"); + ptx = signed_txs.ptx; + + return true; +} +//---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_fee_multiplier(uint32_t priority, bool use_new_fee) const { static const uint64_t old_multipliers[3] = {1, 2, 3}; @@ -2563,7 +2716,6 @@ uint64_t wallet2::get_fee_multiplier(uint32_t priority, bool use_new_fee) const THROW_WALLET_EXCEPTION_IF (false, error::invalid_priority); return 1; } - //---------------------------------------------------------------------------------------------------- // separated the call(s) to wallet2::transfer into their own function // @@ -2613,9 +2765,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto ptx_vector.push_back(ptx); // mark transfers to be used as "spent" - BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers) + BOOST_FOREACH(size_t idx, ptx.selected_transfers) { - set_spent(*it, 0); + set_spent(idx, 0); } } @@ -2625,9 +2777,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto for (auto & ptx : ptx_vector) { // mark transfers to be used as not spent - BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) + BOOST_FOREACH(size_t idx2, ptx.selected_transfers) { - set_unspent(*it2); + set_unspent(idx2); } } @@ -2644,9 +2796,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto for (auto & ptx : ptx_vector) { // mark transfers to be used as not spent - BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) + BOOST_FOREACH(size_t idx2, ptx.selected_transfers) { - set_unspent(*it2); + set_unspent(idx2); } } @@ -2663,9 +2815,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto for (auto & ptx : ptx_vector) { // mark transfers to be used as not spent - BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) + BOOST_FOREACH(size_t idx2, ptx.selected_transfers) { - set_unspent(*it2); + set_unspent(idx2); } } @@ -2675,7 +2827,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto } template<typename entry> -void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<transfer_container::iterator> &selected_transfers, size_t fake_outputs_count) +void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count) { LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count); outs.clear(); @@ -2688,8 +2840,8 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr req_t.jsonrpc = "2.0"; req_t.id = epee::serialization::storage_entry(0); req_t.method = "get_output_histogram"; - for(auto it: selected_transfers) - req_t.params.amounts.push_back(it->is_rct() ? 0 : it->amount()); + for(size_t idx: selected_transfers) + req_t.params.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount()); std::sort(req_t.params.amounts.begin(), req_t.params.amounts.end()); auto end = std::unique(req_t.params.amounts.begin(), req_t.params.amounts.end()); req_t.params.amounts.resize(std::distance(req_t.params.amounts.begin(), end)); @@ -2708,12 +2860,13 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr COMMAND_RPC_GET_OUTPUTS::request req = AUTO_VAL_INIT(req); COMMAND_RPC_GET_OUTPUTS::response daemon_resp = AUTO_VAL_INIT(daemon_resp); - for(transfer_container::iterator it: selected_transfers) + for(size_t idx: selected_transfers) { - const uint64_t amount = it->is_rct() ? 0 : it->amount(); + const transfer_details &td = m_transfers[idx]; + const uint64_t amount = td.is_rct() ? 0 : td.amount(); std::unordered_set<uint64_t> seen_indices; // request more for rct in base recent (locked) coinbases are picked, since they're locked for longer - size_t requested_outputs_count = base_requested_outputs_count + (it->is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); + size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); size_t start = req.outputs.size(); // if there are just enough outputs to mix with, use all of them. @@ -2745,8 +2898,8 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr { // start with real one uint64_t num_found = 1; - seen_indices.emplace(it->m_global_output_index); - req.outputs.push_back({amount, it->m_global_output_index}); + seen_indices.emplace(td.m_global_output_index); + req.outputs.push_back({amount, td.m_global_output_index}); // while we still need more mixins while (num_found < requested_outputs_count) @@ -2798,15 +2951,16 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr std::unordered_map<uint64_t, uint64_t> scanty_outs; size_t base = 0; outs.reserve(selected_transfers.size()); - for(transfer_container::iterator it: selected_transfers) + for(size_t idx: selected_transfers) { - size_t requested_outputs_count = base_requested_outputs_count + (it->is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); + const transfer_details &td = m_transfers[idx]; + size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); outs.push_back(std::vector<entry>()); outs.back().reserve(fake_outputs_count + 1); - const rct::key mask = it->is_rct() ? rct::commit(it->amount(), it->m_mask) : rct::zeroCommit(it->amount()); + const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount()); // pick real out first (it will be sorted when done) - outs.back().push_back(std::make_tuple(it->m_global_output_index, boost::get<txout_to_key>(it->m_tx.vout[it->m_internal_output_index].target).key, mask)); + outs.back().push_back(std::make_tuple(td.m_global_output_index, boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, mask)); // then pick others in random order till we reach the required number // since we use an equiprobable pick here, we don't upset the triangular distribution @@ -2816,12 +2970,12 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr order[n] = n; std::shuffle(order.begin(), order.end(), std::default_random_engine(crypto::rand<unsigned>())); - LOG_PRINT_L2("Looking for " << (fake_outputs_count+1) << " outputs of size " << print_money(it->is_rct() ? 0 : it->amount())); + LOG_PRINT_L2("Looking for " << (fake_outputs_count+1) << " outputs of size " << print_money(td.is_rct() ? 0 : td.amount())); for (size_t o = 0; o < requested_outputs_count && outs.back().size() < fake_outputs_count + 1; ++o) { size_t i = base + order[o]; - LOG_PRINT_L2("Index " << i << "/" << requested_outputs_count << ": idx " << req.outputs[i].index << " (real " << it->m_global_output_index << "), unlocked " << daemon_resp.outs[i].unlocked << ", key " << daemon_resp.outs[i].key); - if (req.outputs[i].index == it->m_global_output_index) // don't re-add real one + LOG_PRINT_L2("Index " << i << "/" << requested_outputs_count << ": idx " << req.outputs[i].index << " (real " << td.m_global_output_index << "), unlocked " << daemon_resp.outs[i].unlocked << ", key " << daemon_resp.outs[i].key); + if (req.outputs[i].index == td.m_global_output_index) // don't re-add real one continue; if (!daemon_resp.outs[i].unlocked) // don't add locked outs continue; @@ -2832,7 +2986,7 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr } if (outs.back().size() < fake_outputs_count + 1) { - scanty_outs[it->is_rct() ? 0 : it->amount()] = outs.back().size(); + scanty_outs[td.is_rct() ? 0 : td.amount()] = outs.back().size(); } else { @@ -2845,18 +2999,19 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr } else { - for (transfer_container::iterator it: selected_transfers) + for (size_t idx: selected_transfers) { + const transfer_details &td = m_transfers[idx]; std::vector<entry> v; - const rct::key mask = it->is_rct() ? rct::commit(it->amount(), it->m_mask) : rct::zeroCommit(it->amount()); - v.push_back(std::make_tuple(it->m_global_output_index, boost::get<txout_to_key>(it->m_tx.vout[it->m_internal_output_index].target).key, mask)); + const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount()); + v.push_back(std::make_tuple(td.m_global_output_index, boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, mask)); outs.push_back(v); } } } template<typename T> -void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, +void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx) { using namespace cryptonote; @@ -2878,9 +3033,9 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent } uint64_t found_money = 0; - BOOST_FOREACH(auto it, selected_transfers) + BOOST_FOREACH(size_t idx, selected_transfers) { - found_money += it->amount(); + found_money += m_transfers[idx].amount(); } LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee)); @@ -2894,11 +3049,11 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent typedef cryptonote::tx_source_entry::output_entry tx_output_entry; size_t i = 0, out_index = 0; std::vector<cryptonote::tx_source_entry> sources; - BOOST_FOREACH(transfer_container::iterator it, selected_transfers) + BOOST_FOREACH(size_t idx, selected_transfers) { sources.resize(sources.size()+1); cryptonote::tx_source_entry& src = sources.back(); - transfer_details& td = *it; + const transfer_details& td = m_transfers[idx]; src.amount = td.amount(); src.rct = td.is_rct(); //paste keys (fake and real) @@ -2983,9 +3138,15 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent ptx.selected_transfers = selected_transfers; ptx.tx_key = tx_key; ptx.dests = dsts; + ptx.construction_data.sources = sources; + ptx.construction_data.destinations = dsts; + ptx.construction_data.change_dts = change_dts; + ptx.construction_data.extra = tx.extra; + ptx.construction_data.unlock_time = unlock_time; + ptx.construction_data.use_rct = false; } -void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, +void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx) { using namespace cryptonote; @@ -3007,9 +3168,9 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry } uint64_t found_money = 0; - BOOST_FOREACH(auto it, selected_transfers) + BOOST_FOREACH(size_t idx, selected_transfers) { - found_money += it->amount(); + found_money += m_transfers[idx].amount(); } LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee)); @@ -3022,11 +3183,11 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry //prepare inputs size_t i = 0, out_index = 0; std::vector<cryptonote::tx_source_entry> sources; - BOOST_FOREACH(transfer_container::iterator it, selected_transfers) + BOOST_FOREACH(size_t idx, selected_transfers) { sources.resize(sources.size()+1); cryptonote::tx_source_entry& src = sources.back(); - transfer_details& td = *it; + const transfer_details& td = m_transfers[idx]; src.amount = td.amount(); src.rct = td.is_rct(); //paste mixin transaction @@ -3096,6 +3257,12 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry ptx.selected_transfers = selected_transfers; ptx.tx_key = tx_key; ptx.dests = dsts; + ptx.construction_data.sources = sources; + ptx.construction_data.destinations = dsts; + ptx.construction_data.change_dts = change_dts; + ptx.construction_data.extra = tx.extra; + ptx.construction_data.unlock_time = unlock_time; + ptx.construction_data.use_rct = true; } static size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs) @@ -3225,7 +3392,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp uint64_t needed_money; uint64_t accumulated_fee, accumulated_outputs, accumulated_change; struct TX { - std::list<transfer_container::iterator> selected_transfers; + std::list<size_t> selected_transfers; std::vector<cryptonote::tx_destination_entry> dsts; cryptonote::transaction tx; pending_tx ptx; @@ -3332,7 +3499,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount())); // add this output to the list to spend - tx.selected_transfers.push_back(m_transfers.begin() + idx); + tx.selected_transfers.push_back(idx); uint64_t available_amount = td.amount(); accumulated_outputs += available_amount; @@ -3400,7 +3567,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); available_for_fee = test_ptx.fee + test_ptx.change_dts.amount + (!test_ptx.dust_added_to_fee ? test_ptx.dust : 0); - LOG_PRINT_L2("Made a " << txBlob.size() << " kB tx, with " << print_money(available_for_fee) << " available for fee (" << + LOG_PRINT_L2("Made a " << ((txBlob.size() + 1023) / 1024) << " kB tx, with " << print_money(available_for_fee) << " available for fee (" << print_money(needed_fee) << " needed)"); if (needed_fee > available_for_fee && dsts[0].amount > 0) @@ -3472,8 +3639,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp { TX &tx = *i; uint64_t tx_money = 0; - for (std::list<transfer_container::iterator>::const_iterator mi = tx.selected_transfers.begin(); mi != tx.selected_transfers.end(); ++mi) - tx_money += (*mi)->amount(); + for (size_t idx: tx.selected_transfers) + tx_money += m_transfers[idx].amount(); LOG_PRINT_L1(" Transaction " << (1+std::distance(txes.begin(), i)) << "/" << txes.size() << ": " << (tx.bytes+1023)/1024 << " kB, sending " << print_money(tx_money) << " in " << tx.selected_transfers.size() << " outputs to " << tx.dsts.size() << " destination(s), including " << @@ -3491,7 +3658,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono std::vector<size_t> unused_dust_indices; uint64_t accumulated_fee, accumulated_outputs, accumulated_change; struct TX { - std::list<transfer_container::iterator> selected_transfers; + std::list<size_t> selected_transfers; std::vector<cryptonote::tx_destination_entry> dsts; cryptonote::transaction tx; pending_tx ptx; @@ -3543,7 +3710,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount())); // add this output to the list to spend - tx.selected_transfers.push_back(m_transfers.begin() + idx); + tx.selected_transfers.push_back(idx); uint64_t available_amount = td.amount(); accumulated_outputs += available_amount; @@ -3576,7 +3743,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); available_for_fee = test_ptx.fee + test_ptx.dests[0].amount + test_ptx.change_dts.amount; - LOG_PRINT_L2("Made a " << txBlob.size() << " kB tx, with " << print_money(available_for_fee) << " available for fee (" << + LOG_PRINT_L2("Made a " << ((txBlob.size() + 1023) / 1024) << " kB tx, with " << print_money(available_for_fee) << " available for fee (" << print_money(needed_fee) << " needed)"); THROW_WALLET_EXCEPTION_IF(needed_fee > available_for_fee, error::wallet_internal_error, "Transaction cannot pay for itself"); @@ -3620,8 +3787,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono { TX &tx = *i; uint64_t tx_money = 0; - for (std::list<transfer_container::iterator>::const_iterator mi = tx.selected_transfers.begin(); mi != tx.selected_transfers.end(); ++mi) - tx_money += (*mi)->amount(); + for (size_t idx: tx.selected_transfers) + tx_money += m_transfers[idx].amount(); LOG_PRINT_L1(" Transaction " << (1+std::distance(txes.begin(), i)) << "/" << txes.size() << ": " << (tx.bytes+1023)/1024 << " kB, sending " << print_money(tx_money) << " in " << tx.selected_transfers.size() << " outputs to " << tx.dsts.size() << " destination(s), including " << @@ -3658,14 +3825,14 @@ void wallet2::transfer_from(const std::vector<size_t> &outs, size_t num_outputs, // select all dust inputs for transaction // throw if there are none uint64_t money = 0; - std::list<transfer_container::iterator> selected_transfers; + std::list<size_t> selected_transfers; #if 1 for (size_t n = 0; n < outs.size(); ++n) { const transfer_details& td = m_transfers[outs[n]]; if (!td.m_spent) { - selected_transfers.push_back (m_transfers.begin() + outs[n]); + selected_transfers.push_back (outs[n]); money += td.amount(); if (selected_transfers.size() >= num_outputs) break; @@ -3693,11 +3860,11 @@ void wallet2::transfer_from(const std::vector<size_t> &outs, size_t num_outputs, //prepare inputs size_t i = 0; std::vector<cryptonote::tx_source_entry> sources; - BOOST_FOREACH(transfer_container::iterator it, selected_transfers) + BOOST_FOREACH(size_t idx, selected_transfers) { sources.resize(sources.size()+1); cryptonote::tx_source_entry& src = sources.back(); - transfer_details& td = *it; + const transfer_details& td = m_transfers[idx]; src.amount = td.amount(); src.rct = td.is_rct(); @@ -3754,6 +3921,12 @@ void wallet2::transfer_from(const std::vector<size_t> &outs, size_t num_outputs, ptx.selected_transfers = selected_transfers; ptx.tx_key = tx_key; ptx.dests = dsts; + ptx.construction_data.sources = sources; + ptx.construction_data.destinations = dsts; + ptx.construction_data.change_dts = change_dts; + ptx.construction_data.extra = tx.extra; + ptx.construction_data.unlock_time = unlock_time; + ptx.construction_data.use_rct = false; } //---------------------------------------------------------------------------------------------------- @@ -3969,9 +4142,9 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo ptx_vector.push_back(ptx); // mark transfers to be used as "spent" - BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers) + BOOST_FOREACH(size_t idx, ptx.selected_transfers) { - set_spent(*it, 0); + set_spent(idx, 0); } } @@ -3981,9 +4154,9 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo for (auto & ptx : ptx_vector) { // mark transfers to be used as not spent - BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) + BOOST_FOREACH(size_t idx2, ptx.selected_transfers) { - set_unspent(*it2); + set_unspent(idx2); } } @@ -3999,9 +4172,9 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo for (auto & ptx : ptx_vector) { // mark transfers to be used as not spent - BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) + BOOST_FOREACH(size_t idx2, ptx.selected_transfers) { - set_unspent(*it2); + set_unspent(idx2); } } @@ -4018,9 +4191,9 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo for (auto & ptx : ptx_vector) { // mark transfers to be used as not spent - BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) + BOOST_FOREACH(size_t idx2, ptx.selected_transfers) { - set_unspent(*it2); + set_unspent(idx2); } } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index dd7cd19dc..c1c8edcd8 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -151,6 +151,25 @@ namespace tools m_amount_in(utd.m_amount_in), m_amount_out(utd.m_amount_out), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp) {} }; + struct tx_construction_data + { + std::vector<cryptonote::tx_source_entry> sources; + std::vector<cryptonote::tx_destination_entry> destinations; + cryptonote::tx_destination_entry change_dts; + std::vector<uint8_t> extra; + uint64_t unlock_time; + bool use_rct; + + BEGIN_SERIALIZE_OBJECT() + FIELD(sources) + FIELD(destinations) + FIELD(change_dts) + FIELD(extra) + VARINT_FIELD(unlock_time) + FIELD(use_rct) + END_SERIALIZE() + }; + typedef std::vector<transfer_details> transfer_container; typedef std::unordered_multimap<crypto::hash, payment_details> payment_container; @@ -160,10 +179,41 @@ namespace tools uint64_t dust, fee; bool dust_added_to_fee; cryptonote::tx_destination_entry change_dts; - std::list<transfer_container::iterator> selected_transfers; + std::list<size_t> selected_transfers; std::string key_images; crypto::secret_key tx_key; std::vector<cryptonote::tx_destination_entry> dests; + + tx_construction_data construction_data; + + BEGIN_SERIALIZE_OBJECT() + FIELD(tx) + VARINT_FIELD(dust) + VARINT_FIELD(fee) + FIELD(dust_added_to_fee) + FIELD(change_dts) + FIELD(selected_transfers) + FIELD(key_images) + FIELD(tx_key) + FIELD(dests) + FIELD(construction_data) + END_SERIALIZE() + }; + + struct unsigned_tx_set + { + std::vector<tx_construction_data> txes; + BEGIN_SERIALIZE_OBJECT() + FIELD(txes) + END_SERIALIZE() + }; + + struct signed_tx_set + { + std::vector<pending_tx> ptx; + BEGIN_SERIALIZE_OBJECT() + FIELD(ptx) + END_SERIALIZE() }; struct keys_file_data @@ -298,13 +348,16 @@ namespace tools template<typename T> void transfer_from(const std::vector<size_t> &outs, size_t num_outputs, uint64_t unlock_time, uint64_t needed_fee, T destination_split_strategy, const tx_dust_policy& dust_policy, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx); template<typename T> - void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, + void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx); - void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, + void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx); void commit_tx(pending_tx& ptx_vector); void commit_tx(std::vector<pending_tx>& ptx_vector); + bool save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename); + bool sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::function<bool(const unsigned_tx_set&)> accept_func = NULL); + bool load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx); std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_all(const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); @@ -407,8 +460,8 @@ namespace tools std::vector<size_t> select_available_unmixable_outputs(bool trusted_daemon); std::vector<size_t> select_available_mixable_outputs(bool trusted_daemon); - size_t pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_dust_indices, const std::list<transfer_container::iterator>& selected_transfers) const; - size_t pop_best_value(std::vector<size_t> &unused_dust_indices, const std::list<transfer_container::iterator>& selected_transfers) const; + size_t pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_dust_indices, const std::list<size_t>& selected_transfers) const; + size_t pop_best_value(std::vector<size_t> &unused_dust_indices, const std::list<size_t>& selected_transfers) const; void set_tx_note(const crypto::hash &txid, const std::string ¬e); std::string get_tx_note(const crypto::hash &txid) const; @@ -446,7 +499,7 @@ namespace tools void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history); void pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::list<cryptonote::block_complete_entry> &prev_blocks, std::list<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, bool &error); void process_blocks(uint64_t start_height, const std::list<cryptonote::block_complete_entry> &blocks, const std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t& blocks_added); - uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<transfer_container::iterator>& selected_transfers, bool trusted_daemon); + uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<size_t>& selected_transfers, bool trusted_daemon); bool prepare_file_names(const std::string& file_path); void process_unconfirmed(const cryptonote::transaction& tx, uint64_t height); void process_outgoing(const cryptonote::transaction& tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received); @@ -462,10 +515,10 @@ namespace tools uint64_t get_fee_multiplier(uint32_t priority, bool use_new_fee) const; float get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const; std::vector<size_t> pick_prefered_rct_inputs(uint64_t needed_money) const; - void set_spent(transfer_details &td, uint64_t height); - void set_unspent(transfer_details &td); + void set_spent(size_t idx, uint64_t height); + void set_unspent(size_t idx); template<typename entry> - void get_outs(std::vector<std::vector<entry>> &outs, const std::list<transfer_container::iterator> &selected_transfers, size_t fake_outputs_count); + void get_outs(std::vector<std::vector<entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count); cryptonote::account_base m_account; std::string m_daemon_address; @@ -737,7 +790,7 @@ namespace tools // randomly select inputs for transaction // throw if requested send amount is greater than amount available to send - std::list<transfer_container::iterator> selected_transfers; + std::list<size_t> selected_transfers; uint64_t found_money = select_transfers(needed_money, unused_transfers_indices, selected_transfers, trusted_daemon); THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee); @@ -749,8 +802,9 @@ namespace tools { COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request req = AUTO_VAL_INIT(req); req.outs_count = fake_outputs_count + 1;// add one to make possible (if need) to skip real output key - BOOST_FOREACH(transfer_container::iterator it, selected_transfers) + BOOST_FOREACH(size_t idx, selected_transfers) { + const transfer_container::const_iterator it = m_transfers.begin() + idx; THROW_WALLET_EXCEPTION_IF(it->m_tx.vout.size() <= it->m_internal_output_index, error::wallet_internal_error, "m_internal_output_index = " + std::to_string(it->m_internal_output_index) + " is greater or equal to outputs count = " + std::to_string(it->m_tx.vout.size())); @@ -781,11 +835,11 @@ namespace tools //prepare inputs size_t i = 0; std::vector<cryptonote::tx_source_entry> sources; - BOOST_FOREACH(transfer_container::iterator it, selected_transfers) + BOOST_FOREACH(size_t idx, selected_transfers) { sources.resize(sources.size()+1); cryptonote::tx_source_entry& src = sources.back(); - transfer_details& td = *it; + const transfer_details& td = m_transfers[idx]; src.amount = td.amount(); src.rct = false; //paste mixin transaction @@ -872,6 +926,12 @@ namespace tools ptx.selected_transfers = selected_transfers; ptx.tx_key = tx_key; ptx.dests = dsts; + ptx.construction_data.sources = sources; + ptx.construction_data.destinations = dsts; + ptx.construction_data.change_dts = change_dts; + ptx.construction_data.extra = tx.extra; + ptx.construction_data.unlock_time = unlock_time; + ptx.construction_data.use_rct = false; } |