diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/api/transaction_history.cpp | 2 | ||||
-rw-r--r-- | src/wallet/api/wallet.cpp | 9 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 78 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 2 | ||||
-rw-r--r-- | src/wallet/wallet_errors.h | 27 |
5 files changed, 100 insertions, 18 deletions
diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp index e4a003b02..2ba5f3620 100644 --- a/src/wallet/api/transaction_history.cpp +++ b/src/wallet/api/transaction_history.cpp @@ -156,8 +156,8 @@ void TransactionHistoryImpl::refresh() const crypto::hash &hash = i->first; const tools::wallet2::confirmed_transfer_details &pd = i->second; - uint64_t fee = pd.m_amount_in - pd.m_amount_out; uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change; // change may not be known + uint64_t fee = pd.m_amount_in - pd.m_amount_out - change; std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id); diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index d1c849537..94d62666a 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -595,6 +595,15 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const m_status = Status_Error; std::ostringstream writer; + writer << boost::format(tr("not enough money to transfer, available only %s, sent amount %s")) % + print_money(e.available()) % + print_money(e.tx_amount()); + m_errorString = writer.str(); + + } catch (const tools::error::tx_not_possible& e) { + m_status = Status_Error; + std::ostringstream writer; + writer << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) % print_money(e.available()) % print_money(e.tx_amount() + e.fee()) % diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index d09e99518..b499f6b24 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -77,6 +77,9 @@ using namespace cryptonote; #define UNSIGNED_TX_PREFIX "Monero unsigned tx set\001" #define SIGNED_TX_PREFIX "Monero signed tx set\001" +#define RECENT_OUTPUT_RATIO (0.25) // 25% of outputs are from the recent zone +#define RECENT_OUTPUT_ZONE (5 * 86400) // last 5 days are the recent zone + #define KILL_IOSERVICE() \ do { \ work.reset(); \ @@ -2283,9 +2286,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 (size_t i = 0; i < selected_transfers.size(); ++i) + for (std::list<size_t>::const_iterator i = selected_transfers.begin(); i != selected_transfers.end(); ++i) { - float r = get_output_relatedness(candidate, transfers[i]); + float r = get_output_relatedness(candidate, transfers[*i]); if (r > relatedness) { relatedness = r; @@ -2854,6 +2857,7 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<si 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)); req_t.params.unlocked = true; + req_t.params.recent_cutoff = time(NULL) - RECENT_OUTPUT_ZONE; bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected"); @@ -2868,8 +2872,10 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<si COMMAND_RPC_GET_OUTPUTS::request req = AUTO_VAL_INIT(req); COMMAND_RPC_GET_OUTPUTS::response daemon_resp = AUTO_VAL_INIT(daemon_resp); + size_t num_selected_transfers = 0; for(size_t idx: selected_transfers) { + ++num_selected_transfers; const transfer_details &td = m_transfers[idx]; const uint64_t amount = td.is_rct() ? 0 : td.amount(); std::unordered_set<uint64_t> seen_indices; @@ -2879,18 +2885,33 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<si // if there are just enough outputs to mix with, use all of them. // Eventually this should become impossible. - uint64_t num_outs = 0; + uint64_t num_outs = 0, num_recent_outs = 0; for (auto he: resp_t.result.histogram) { if (he.amount == amount) { - num_outs = he.instances; + LOG_PRINT_L2("Found " << print_money(amount) << ": " << he.total_instances << " total, " + << he.unlocked_instances << " unlocked, " << he.recent_instances << " recent"); + num_outs = he.unlocked_instances; + num_recent_outs = he.recent_instances; break; } } LOG_PRINT_L1("" << num_outs << " outputs of size " << print_money(amount)); THROW_WALLET_EXCEPTION_IF(num_outs == 0, error::wallet_internal_error, "histogram reports no outputs for " + boost::lexical_cast<std::string>(amount) + ", not even ours"); + THROW_WALLET_EXCEPTION_IF(num_recent_outs > num_outs, error::wallet_internal_error, + "histogram reports more recent outs than outs for " + boost::lexical_cast<std::string>(amount)); + + // X% of those outs are to be taken from recent outputs + size_t recent_outputs_count = requested_outputs_count * RECENT_OUTPUT_RATIO; + if (recent_outputs_count == 0) + recent_outputs_count = 1; // ensure we have at least one, if possible + if (recent_outputs_count > num_recent_outs) + recent_outputs_count = num_recent_outs; + if (td.m_global_output_index >= num_outs - num_recent_outs) + --recent_outputs_count; // if the real out is recent, pick one less recent fake out + LOG_PRINT_L1("Using " << recent_outputs_count << " recent outputs"); if (num_outs <= requested_outputs_count) { @@ -2920,11 +2941,24 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<si // return to the top of the loop and try again, otherwise add it to the // list of output indices we've seen. - // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit - uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53); - double frac = std::sqrt((double)r / ((uint64_t)1 << 53)); - uint64_t i = (uint64_t)(frac*num_outs); - // just in case rounding up to 1 occurs after sqrt + uint64_t i; + if (num_found - 1 < recent_outputs_count) // -1 to account for the real one we seeded with + { + // equiprobable distribution over the recent outs + uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53); + double frac = std::sqrt((double)r / ((uint64_t)1 << 53)); + i = (uint64_t)(frac*num_recent_outs) + num_outs - num_recent_outs; + LOG_PRINT_L2("picking " << i << " as recent"); + } + else + { + // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit + uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53); + double frac = std::sqrt((double)r / ((uint64_t)1 << 53)); + i = (uint64_t)(frac*num_outs); + LOG_PRINT_L2("picking " << i << " as triangular"); + } + // just in case rounding up to 1 occurs after calc if (i == num_outs) --i; @@ -2958,7 +2992,7 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<si std::unordered_map<uint64_t, uint64_t> scanty_outs; size_t base = 0; - outs.reserve(selected_transfers.size()); + outs.reserve(num_selected_transfers); for(size_t idx: selected_transfers) { const transfer_details &td = m_transfers[idx]; @@ -3456,6 +3490,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } LOG_PRINT_L2("Starting with " << unused_transfers_indices.size() << " non-dust outputs and " << unused_dust_indices.size() << " dust outputs"); + // 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 + THROW_WALLET_EXCEPTION_IF(needed_money > unlocked_balance(), error::not_enough_money, + unlocked_balance(), needed_money, 0); + if (unused_dust_indices.empty() && unused_transfers_indices.empty()) return std::vector<wallet2::pending_tx>(); @@ -3496,7 +3536,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // if we need to spend money and don't have any left, we fail if (unused_dust_indices.empty() && unused_transfers_indices.empty()) { LOG_PRINT_L2("No more outputs to choose from"); - THROW_WALLET_EXCEPTION_IF(1, error::not_enough_money, unlocked_balance(), needed_money, accumulated_fee + needed_fee); + THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(), needed_money, accumulated_fee + needed_fee); } // get a random unspent output and use it to pay part (or all) of the current destination (and maybe next one, etc) @@ -3636,7 +3676,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp if (adding_fee) { LOG_PRINT_L1("We ran out of outputs while trying to gather final fee"); - THROW_WALLET_EXCEPTION_IF(1, error::not_enough_money, unlocked_balance(), needed_money, accumulated_fee + needed_fee); + THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(), needed_money, accumulated_fee + needed_fee); } LOG_PRINT_L1("Done creating " << txes.size() << " transactions, " << print_money(accumulated_fee) << @@ -3696,7 +3736,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton uint64_t needed_fee, available_for_fee = 0; uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit(); - const bool use_rct = use_fork_rules(4, 0); + const bool use_rct = fake_outs_count > 0 && use_fork_rules(4, 0); const bool use_new_fee = use_fork_rules(3, -720 * 14); const uint64_t fee_per_kb = use_new_fee ? FEE_PER_KB : FEE_PER_KB_OLD; const uint64_t fee_multiplier = get_fee_multiplier(priority, use_new_fee); @@ -3974,20 +4014,26 @@ uint64_t wallet2::get_num_rct_outputs() THROW_WALLET_EXCEPTION_IF(resp_t.result.histogram.size() != 1, error::get_histogram_error, "Expected exactly one response"); THROW_WALLET_EXCEPTION_IF(resp_t.result.histogram[0].amount != 0, error::get_histogram_error, "Expected 0 amount"); - return resp_t.result.histogram[0].instances; + return resp_t.result.histogram[0].total_instances; +} +//---------------------------------------------------------------------------------------------------- +const wallet2::transfer_details &wallet2::get_transfer_details(size_t idx) const +{ + THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error, "Bad transfer index"); + return m_transfers[idx]; } //---------------------------------------------------------------------------------------------------- std::vector<size_t> wallet2::select_available_unmixable_outputs(bool trusted_daemon) { // request all outputs with less than 3 instances - const size_t min_mixin = use_fork_rules(5, 10) ? 2 : 4; // v5 increases min mixin from 2 to 4 + const size_t min_mixin = use_fork_rules(5, 10) ? 4 : 2; // v5 increases min mixin from 2 to 4 return select_available_outputs_from_histogram(min_mixin + 1, false, true, trusted_daemon); } //---------------------------------------------------------------------------------------------------- std::vector<size_t> wallet2::select_available_mixable_outputs(bool trusted_daemon) { // request all outputs with at least 3 instances, so we can use mixin 2 with - const size_t min_mixin = use_fork_rules(5, 10) ? 2 : 4; // v5 increases min mixin from 2 to 4 + const size_t min_mixin = use_fork_rules(5, 10) ? 4 : 2; // v5 increases min mixin from 2 to 4 return select_available_outputs_from_histogram(min_mixin + 1, true, true, trusted_daemon); } //---------------------------------------------------------------------------------------------------- diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 20e157b9c..fa9797219 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -422,7 +422,6 @@ namespace tools * \return Whether path is valid format */ static bool wallet_valid_path_format(const std::string& file_path); - static bool parse_long_payment_id(const std::string& payment_id_str, crypto::hash& payment_id); static bool parse_short_payment_id(const std::string& payment_id_str, crypto::hash8& payment_id); static bool parse_payment_id(const std::string& payment_id_str, crypto::hash& payment_id); @@ -447,6 +446,7 @@ namespace tools bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) const; uint64_t get_num_rct_outputs(); + const transfer_details &get_transfer_details(size_t idx) const; void get_hard_fork_info(uint8_t version, uint64_t &earliest_height); bool use_fork_rules(uint8_t version, int64_t early_blocks = 0); diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index c5590d79c..93e7c2ec3 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -68,6 +68,7 @@ namespace tools // transfer_error * // get_random_outs_general_error // not_enough_money + // tx_not_possible // not_enough_outs_to_mix // tx_not_constructed // tx_rejected @@ -351,6 +352,32 @@ namespace tools : transfer_error(std::move(loc), "not enough money") , m_available(availbable) , m_tx_amount(tx_amount) + { + } + + uint64_t available() const { return m_available; } + uint64_t tx_amount() const { return m_tx_amount; } + + std::string to_string() const + { + std::ostringstream ss; + ss << transfer_error::to_string() << + ", available = " << cryptonote::print_money(m_available) << + ", tx_amount = " << cryptonote::print_money(m_tx_amount); + return ss.str(); + } + + private: + uint64_t m_available; + uint64_t m_tx_amount; + }; + //---------------------------------------------------------------------------------------------------- + struct tx_not_possible : public transfer_error + { + explicit tx_not_possible(std::string&& loc, uint64_t availbable, uint64_t tx_amount, uint64_t fee) + : transfer_error(std::move(loc), "tx not possible") + , m_available(availbable) + , m_tx_amount(tx_amount) , m_fee(fee) { } |