aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/api/transaction_history.cpp2
-rw-r--r--src/wallet/api/wallet.cpp9
-rw-r--r--src/wallet/wallet2.cpp78
-rw-r--r--src/wallet/wallet2.h2
-rw-r--r--src/wallet/wallet_errors.h27
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)
{
}