diff options
Diffstat (limited to 'src/wallet/wallet2.cpp')
-rw-r--r-- | src/wallet/wallet2.cpp | 217 |
1 files changed, 163 insertions, 54 deletions
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index cc0e9e7e2..a7161ffcb 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -305,9 +305,9 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, address, std::string, String, false, std::string()); // compatibility checks - if (!field_seed_found && !field_viewkey_found) + if (!field_seed_found && !field_viewkey_found && !field_spendkey_found) { - tools::fail_msg_writer() << tools::wallet2::tr("At least one of Electrum-style word list and private view key must be specified"); + tools::fail_msg_writer() << tools::wallet2::tr("At least one of Electrum-style word list and private view key and private spend key must be specified"); return false; } if (field_seed_found && (field_viewkey_found || field_spendkey_found)) @@ -368,6 +368,10 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, { wallet->generate(field_filename, field_password, recovery_key, recover, false); } + else if (field_viewkey.empty() && !field_spendkey.empty()) + { + wallet->generate(field_filename, field_password, spendkey, recover, false); + } else { cryptonote::account_public_address address; @@ -390,6 +394,11 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, } address.m_spend_public_key = info.address.m_spend_public_key; } + else + { + tools::fail_msg_writer() << tools::wallet2::tr("Address must be specified in order to create watch-only wallet"); + return false; + } wallet->generate(field_filename, field_password, address, viewkey); } else @@ -444,6 +453,21 @@ std::string strjoin(const std::vector<size_t> &V, const char *sep) return ss.str(); } +static void emplace_or_replace(std::unordered_multimap<crypto::hash, tools::wallet2::pool_payment_details> &container, + const crypto::hash &key, const tools::wallet2::pool_payment_details &pd) +{ + auto range = container.equal_range(key); + for (auto i = range.first; i != range.second; ++i) + { + if (i->second.m_pd.m_tx_hash == pd.m_pd.m_tx_hash) + { + i->second = pd; + return; + } + } + container.emplace(key, pd); +} + } //namespace namespace tools @@ -692,20 +716,20 @@ void wallet2::expand_subaddresses(const cryptonote::subaddress_index& index) //---------------------------------------------------------------------------------------------------- std::string wallet2::get_subaddress_label(const cryptonote::subaddress_index& index) const { - if (index.major >= m_subaddress_labels.size()) - throw std::runtime_error("index.major is out of bound"); - if (index.minor >= m_subaddress_labels[index.major].size()) - throw std::runtime_error("index.minor is out of bound"); + if (index.major >= m_subaddress_labels.size() || index.minor >= m_subaddress_labels[index.major].size()) + { + MERROR("Subaddress label doesn't exist"); + return ""; + } return m_subaddress_labels[index.major][index.minor]; } //---------------------------------------------------------------------------------------------------- void wallet2::set_subaddress_label(const cryptonote::subaddress_index& index, const std::string &label) { - if (index.major >= m_subaddress_labels.size()) - throw std::runtime_error("index.major is out of bound"); - if (index.minor >= m_subaddress_labels[index.major].size()) - throw std::runtime_error("index.minor is out of bound"); - m_subaddress_labels[index.major][index.minor] = label; + if (index.major >= m_subaddress_labels.size() || index.minor >= m_subaddress_labels[index.major].size()) + MERROR("Subaddress index is out of bounds. Failed to set subaddress label."); + else + m_subaddress_labels[index.major][index.minor] = label; } //---------------------------------------------------------------------------------------------------- /*! @@ -788,12 +812,12 @@ void wallet2::scan_output(const cryptonote::account_keys &keys, const cryptonote { tx_scan_info.money_transfered = tools::decodeRct(tx.rct_signatures, tx_scan_info.received->derivation, i, tx_scan_info.mask); } - tx_money_got_in_outs[tx_scan_info.received->index] = tx_scan_info.money_transfered; + tx_money_got_in_outs[tx_scan_info.received->index] += tx_scan_info.money_transfered; tx_scan_info.amount = tx_scan_info.money_transfered; ++num_vouts_received; } //---------------------------------------------------------------------------------------------------- -void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool) +void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen) { // In this function, tx (probably) only contains the base information // (that is, the prunable stuff may or may not be included) @@ -1163,7 +1187,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote payment.m_timestamp = ts; payment.m_subaddr_index = i.first; if (pool) { - m_unconfirmed_payments.emplace(payment_id, payment); + emplace_or_replace(m_unconfirmed_payments, payment_id, pool_payment_details{payment, double_spend_seen}); if (0 != m_callback) m_callback->on_unconfirmed_money_received(height, txid, tx, payment.m_amount, payment.m_subaddr_index); } @@ -1241,7 +1265,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(get_transaction_hash(b.miner_tx), b.miner_tx, o_indices.indices[txidx++].indices, height, b.timestamp, true, false); + process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, o_indices.indices[txidx++].indices, height, b.timestamp, true, false, false); TIME_MEASURE_FINISH(miner_tx_handle_time); TIME_MEASURE_START(txs_handle_time); @@ -1252,7 +1276,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry cryptonote::transaction tx; bool r = parse_and_validate_tx_base_from_blob(txblob, tx); THROW_WALLET_EXCEPTION_IF(!r, error::tx_parse_error, txblob); - process_new_transaction(b.tx_hashes[idx], tx, o_indices.indices[txidx++].indices, height, b.timestamp, false, false); + process_new_transaction(b.tx_hashes[idx], tx, o_indices.indices[txidx++].indices, height, b.timestamp, false, false, false); ++idx; } TIME_MEASURE_FINISH(txs_handle_time); @@ -1520,10 +1544,10 @@ void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_hei void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes) { // 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(); + std::unordered_multimap<crypto::hash, wallet2::pool_payment_details>::iterator uit = m_unconfirmed_payments.begin(); while (uit != m_unconfirmed_payments.end()) { - const crypto::hash &txid = uit->second.m_tx_hash; + const crypto::hash &txid = uit->second.m_pd.m_tx_hash; bool found = false; for (const auto &it2: tx_hashes) { @@ -1626,23 +1650,27 @@ void wallet2::update_pool_state(bool refreshed) MDEBUG("update_pool_state done second loop"); // gather txids of new pool txes to us - std::vector<crypto::hash> txids; + std::vector<std::pair<crypto::hash, bool>> txids; for (const auto &txid: res.tx_hashes) { - if (m_scanned_pool_txs[0].find(txid) != m_scanned_pool_txs[0].end() || m_scanned_pool_txs[1].find(txid) != m_scanned_pool_txs[1].end()) - { - LOG_PRINT_L2("Already seen " << txid << ", skipped"); - continue; - } bool txid_found_in_up = false; for (const auto &up: m_unconfirmed_payments) { - if (up.second.m_tx_hash == txid) + if (up.second.m_pd.m_tx_hash == txid) { txid_found_in_up = true; break; } } + if (m_scanned_pool_txs[0].find(txid) != m_scanned_pool_txs[0].end() || m_scanned_pool_txs[1].find(txid) != m_scanned_pool_txs[1].end()) + { + // if it's for us, we want to keep track of whether we saw a double spend, so don't bail out + if (!txid_found_in_up) + { + LOG_PRINT_L2("Already seen " << txid << ", and not for us, skipped"); + continue; + } + } if (!txid_found_in_up) { LOG_PRINT_L1("Found new pool tx: " << txid); @@ -1670,7 +1698,7 @@ void wallet2::update_pool_state(bool refreshed) if (!found) { // not one of those we sent ourselves - txids.push_back(txid); + txids.push_back({txid, false}); } else { @@ -1680,6 +1708,7 @@ void wallet2::update_pool_state(bool refreshed) else { LOG_PRINT_L1("Already saw that one, it's for us"); + txids.push_back({txid, true}); } } @@ -1688,8 +1717,8 @@ void wallet2::update_pool_state(bool refreshed) { cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; - for (const auto &txid: txids) - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + for (const auto &p: txids) + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(p.first)); MDEBUG("asking for " << txids.size() << " transactions"); req.decode_as_json = false; m_daemon_rpc_mutex.lock(); @@ -1711,10 +1740,11 @@ void wallet2::update_pool_state(bool refreshed) { if (cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash)) { - const std::vector<crypto::hash>::const_iterator i = std::find(txids.begin(), txids.end(), tx_hash); + const std::vector<std::pair<crypto::hash, bool>>::const_iterator i = std::find_if(txids.begin(), txids.end(), + [tx_hash](const std::pair<crypto::hash, bool> &e) { return e.first == tx_hash; }); if (i != txids.end()) { - process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, time(NULL), false, true); + process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, time(NULL), false, true, tx_entry.double_spend_seen); m_scanned_pool_txs[0].insert(tx_hash); if (m_scanned_pool_txs[0].size() > 5000) { @@ -1759,8 +1789,21 @@ void wallet2::update_pool_state(bool refreshed) void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history) { std::list<crypto::hash> hashes; - size_t current_index = m_blockchain.size(); + const uint64_t checkpoint_height = m_checkpoints.get_max_height(); + if (stop_height > checkpoint_height && m_blockchain.size()-1 < checkpoint_height) + { + // we will drop all these, so don't bother getting them + uint64_t missing_blocks = m_checkpoints.get_max_height() - m_blockchain.size(); + while (missing_blocks-- > 0) + m_blockchain.push_back(crypto::null_hash); // maybe a bit suboptimal, but deque won't do huge reallocs like vector + m_blockchain.push_back(m_checkpoints.get_points().at(checkpoint_height)); + m_local_bc_height = m_blockchain.size(); + short_chain_history.clear(); + get_short_chain_history(short_chain_history); + } + + size_t current_index = m_blockchain.size(); while(m_run.load(std::memory_order_relaxed) && current_index < stop_height) { pull_hashes(0, blocks_start_height, short_chain_history, hashes); @@ -3073,11 +3116,11 @@ 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 boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const +void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::pool_payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const { for (auto i = m_unconfirmed_payments.begin(); i != m_unconfirmed_payments.end(); ++i) { - if ((!subaddr_account || *subaddr_account == i->second.m_subaddr_index.major) && - (subaddr_indices.empty() || subaddr_indices.count(i->second.m_subaddr_index.minor) == 1)) + if ((!subaddr_account || *subaddr_account == i->second.m_pd.m_subaddr_index.major) && + (subaddr_indices.empty() || subaddr_indices.count(i->second.m_pd.m_subaddr_index.minor) == 1)) unconfirmed_payments.push_back(*i); } } @@ -3272,7 +3315,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<size_t>& selected_transfers, bool smallest) const +size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_indices, const std::vector<size_t>& selected_transfers, bool smallest) const { std::vector<size_t> candidates; float best_relatedness = 1.0f; @@ -3280,7 +3323,7 @@ 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 (std::list<size_t>::const_iterator i = selected_transfers.begin(); i != selected_transfers.end(); ++i) + for (std::vector<size_t>::const_iterator i = selected_transfers.begin(); i != selected_transfers.end(); ++i) { float r = get_output_relatedness(candidate, transfers[*i]); if (r > relatedness) @@ -3321,7 +3364,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<size_t>& selected_transfers, bool smallest) const +size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::vector<size_t>& selected_transfers, bool smallest) const { return pop_best_value_from(m_transfers, unused_indices, selected_transfers, smallest); } @@ -3330,9 +3373,10 @@ 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<size_t>& selected_transfers, bool trusted_daemon) +uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::vector<size_t>& selected_transfers, bool trusted_daemon) { uint64_t found_money = 0; + selected_transfers.reserve(unused_transfers_indices.size()); while (found_money < needed_money && !unused_transfers_indices.empty()) { size_t idx = pop_best_value(unused_transfers_indices, selected_transfers); @@ -3950,6 +3994,19 @@ int wallet2::get_fee_algorithm() return 1; return 0; } +//------------------------------------------------------------------------------------------------------------------------------ +uint64_t wallet2::adjust_mixin(uint64_t mixin) +{ + if (mixin < 4 && use_fork_rules(6, 10)) { + MWARNING("Requested ring size " << (mixin + 1) << " too low for hard fork 6, using 5"); + mixin = 4; + } + else if (mixin < 2 && use_fork_rules(2, 10)) { + MWARNING("Requested ring size " << (mixin + 1) << " too low for hard fork 2, using 3"); + mixin = 2; + } + return mixin; +} //---------------------------------------------------------------------------------------------------- // separated the call(s) to wallet2::transfer into their own function // @@ -4073,7 +4130,7 @@ bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_out return true; } -void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count) { +void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count) { MDEBUG("LIGHTWALLET - Getting random outs"); @@ -4177,7 +4234,7 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_ } } -void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count) +void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count) { LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count); outs.clear(); @@ -4412,7 +4469,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> } template<typename T> -void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count, +void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, 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) { @@ -4441,7 +4498,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent } LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee)); - THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee); + THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_unlocked_money, found_money, needed_money - fee, fee); uint32_t subaddr_account = m_transfers[*selected_transfers.begin()].m_subaddr_index.major; for (auto i = ++selected_transfers.begin(); i != selected_transfers.end(); ++i) @@ -4566,7 +4623,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent LOG_PRINT_L2("transfer_selected done"); } -void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count, +void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx) { @@ -4598,7 +4655,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry } LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee)); - THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee); + THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_unlocked_money, found_money, needed_money - fee, fee); uint32_t subaddr_account = m_transfers[*selected_transfers.begin()].m_subaddr_index.major; for (auto i = ++selected_transfers.begin(); i != selected_transfers.end(); ++i) @@ -4751,7 +4808,7 @@ static size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs) size += (2*64*32+32+64*32) * n_outputs; // MGs - size += n_inputs * (32 * (mixin+1) + 32); + size += n_inputs * (64 * (mixin+1) + 32); // mixRing - not serialized, can be reconstructed /* size += 2 * 32 * (mixin+1) * n_inputs; */ @@ -5129,7 +5186,7 @@ void wallet2::light_wallet_get_address_txs() payments_txs.push_back(p.second.m_tx_hash); std::vector<crypto::hash> unconfirmed_payments_txs; for(const auto &up: m_unconfirmed_payments) - unconfirmed_payments_txs.push_back(up.second.m_tx_hash); + unconfirmed_payments_txs.push_back(up.second.m_pd.m_tx_hash); // for balance calculation uint64_t wallet_total_sent = 0; @@ -5195,7 +5252,11 @@ void wallet2::light_wallet_get_address_txs() if (t.mempool) { if (std::find(unconfirmed_payments_txs.begin(), unconfirmed_payments_txs.end(), tx_hash) == unconfirmed_payments_txs.end()) { pool_txs.push_back(tx_hash); - m_unconfirmed_payments.emplace(tx_hash, payment); + // assume false as we don't get that info from the light wallet server + crypto::hash payment_id; + THROW_WALLET_EXCEPTION_IF(!epee::string_tools::hex_to_pod(t.payment_id, payment_id), + error::wallet_internal_error, "Failed to parse payment id"); + emplace_or_replace(m_unconfirmed_payments, payment_id, pool_payment_details{payment, false}); if (0 != m_callback) { m_callback->on_lw_unconfirmed_money_received(t.height, payment.m_tx_hash, payment.m_amount); } @@ -5325,11 +5386,27 @@ bool wallet2::light_wallet_key_image_is_ours(const crypto::key_image& key_image, crypto::key_image calculated_key_image; cryptonote::keypair in_ephemeral; - // Subaddresses aren't supported in mymonero/openmonero yet. Using empty values. - const std::vector<crypto::public_key> additional_tx_pub_keys; - const crypto::public_key pkey = crypto::null_pkey; - - cryptonote::generate_key_image_helper(get_account().get_keys(), m_subaddresses, pkey, tx_public_key, additional_tx_pub_keys, out_index, in_ephemeral, calculated_key_image); + // Subaddresses aren't supported in mymonero/openmonero yet. Roll out the original scheme: + // compute D = a*R + // compute P = Hs(D || i)*G + B + // compute x = Hs(D || i) + b (and check if P==x*G) + // compute I = x*Hp(P) + const account_keys& ack = get_account().get_keys(); + crypto::key_derivation derivation; + bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, derivation); + CHECK_AND_ASSERT_MES(r, false, "failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); + + r = crypto::derive_public_key(derivation, out_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub); + CHECK_AND_ASSERT_MES(r, false, "failed to derive_public_key (" << derivation << ", " << out_index << ", " << ack.m_account_address.m_spend_public_key << ")"); + + crypto::derive_secret_key(derivation, out_index, ack.m_spend_secret_key, in_ephemeral.sec); + crypto::public_key out_pkey_test; + r = crypto::secret_key_to_public_key(in_ephemeral.sec, out_pkey_test); + CHECK_AND_ASSERT_MES(r, false, "failed to secret_key_to_public_key(" << in_ephemeral.sec << ")"); + CHECK_AND_ASSERT_MES(in_ephemeral.pub == out_pkey_test, false, "derived secret key doesn't match derived public key"); + + crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, calculated_key_image); + index_keyimage_map.emplace(out_index, calculated_key_image); m_key_image_cache.emplace(tx_public_key, index_keyimage_map); return key_image == calculated_key_image; @@ -5361,7 +5438,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<size_t> selected_transfers; + std::vector<size_t> selected_transfers; std::vector<cryptonote::tx_destination_entry> dsts; cryptonote::transaction tx; pending_tx ptx; @@ -5475,6 +5552,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } } + // early out if we know we can't make it anyway + // we could also check for being within FEE_PER_KB, but if the fee calculation + // ever changes, this might be missed, so let this go through + // first check overall balance is enough, then unlocked one, so we throw distinct exceptions + THROW_WALLET_EXCEPTION_IF(needed_money > balance(subaddr_account), error::not_enough_money, + unlocked_balance(subaddr_account), needed_money, 0); + THROW_WALLET_EXCEPTION_IF(needed_money > unlocked_balance(subaddr_account), error::not_enough_unlocked_money, + unlocked_balance(subaddr_account), needed_money, 0); + // shuffle & sort output indices { std::random_device rd; @@ -5835,7 +5921,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton { uint64_t accumulated_fee, accumulated_outputs, accumulated_change; struct TX { - std::list<size_t> selected_transfers; + std::vector<size_t> selected_transfers; std::vector<cryptonote::tx_destination_entry> dsts; cryptonote::transaction tx; pending_tx ptx; @@ -6248,6 +6334,29 @@ std::string wallet2::get_tx_note(const crypto::hash &txid) const return i->second; } +void wallet2::set_attribute(const std::string &key, const std::string &value) +{ + m_attributes[key] = value; +} + +std::string wallet2::get_attribute(const std::string &key) const +{ + std::unordered_map<std::string, std::string>::const_iterator i = m_attributes.find(key); + if (i == m_attributes.end()) + return std::string(); + return i->second; +} + +void wallet2::set_description(const std::string &description) +{ + set_attribute(ATTRIBUTE_DESCRIPTION, description); +} + +std::string wallet2::get_description() const +{ + return get_attribute(ATTRIBUTE_DESCRIPTION); +} + std::string wallet2::sign(const std::string &data) const { crypto::hash hash; |