aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/wallet2.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/wallet2.cpp')
-rw-r--r--src/wallet/wallet2.cpp1059
1 files changed, 856 insertions, 203 deletions
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 8502353ed..99866dbeb 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -29,6 +29,7 @@
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include <random>
+#include <tuple>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
@@ -55,6 +56,7 @@ using namespace epee;
#include "rapidjson/stringbuffer.h"
#include "common/json_util.h"
#include "common/base58.h"
+#include "ringct/rctSigs.h"
extern "C"
{
@@ -96,14 +98,18 @@ void do_prepare_file_names(const std::string& file_path, std::string& keys_file,
}
}
-uint64_t calculate_fee(const cryptonote::blobdata &blob, uint64_t fee_multiplier)
+uint64_t calculate_fee(size_t bytes, uint64_t fee_multiplier)
{
THROW_WALLET_EXCEPTION_IF(fee_multiplier <= 0 || fee_multiplier > 3, tools::error::invalid_fee_multiplier);
- uint64_t bytes = blob.size();
uint64_t kB = (bytes + 1023) / 1024;
return kB * FEE_PER_KB * fee_multiplier;
}
+uint64_t calculate_fee(const cryptonote::blobdata &blob, uint64_t fee_multiplier)
+{
+ return calculate_fee(blob.size(), fee_multiplier);
+}
+
} //namespace
namespace tools
@@ -168,7 +174,21 @@ bool wallet2::is_deprecated() const
return is_old_file_format;
}
//----------------------------------------------------------------------------------------------------
-void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const crypto::public_key &tx_pub_key, size_t i, uint64_t &money_transfered, bool &error) const
+void wallet2::set_spent(transfer_details &td, uint64_t height)
+{
+ 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)
+{
+ 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;
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const crypto::public_key &tx_pub_key, size_t i, bool &received, uint64_t &money_transfered, bool &error) const
{
if (o.target.type() != typeid(txout_to_key))
{
@@ -176,9 +196,10 @@ void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const cryp
LOG_ERROR("wrong type id in transaction out");
return;
}
- if(is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, i))
+ received = is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, i);
+ if(received)
{
- money_transfered = o.amount;
+ money_transfered = o.amount; // may be 0 for ringct outputs
}
else
{
@@ -187,8 +208,57 @@ void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const cryp
error = false;
}
//----------------------------------------------------------------------------------------------------
-void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_t height, uint64_t ts, bool miner_tx, bool pool)
+static uint64_t decodeRct(const rct::rctSig & rv, const crypto::public_key pub, const crypto::secret_key &sec, unsigned int i, rct::key & mask)
{
+ crypto::key_derivation derivation;
+ bool r = crypto::generate_key_derivation(pub, sec, derivation);
+ if (!r)
+ {
+ LOG_ERROR("Failed to generate key derivation to decode rct output " << i);
+ return 0;
+ }
+ crypto::secret_key scalar1;
+ crypto::derivation_to_scalar(derivation, i, scalar1);
+ try
+ {
+ switch (rv.type)
+ {
+ case rct::RCTTypeSimple:
+ return rct::decodeRctSimple(rv, rct::sk2rct(scalar1), i, mask);
+ case rct::RCTTypeFull:
+ return rct::decodeRct(rv, rct::sk2rct(scalar1), i, mask);
+ default:
+ LOG_ERROR("Unsupported rct type: " << rv.type);
+ return 0;
+ }
+ }
+ catch (const std::exception &e)
+ {
+ LOG_ERROR("Failed to decode input " << i);
+ return 0;
+ }
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::process_new_transaction(const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool)
+{
+ class lazy_txid_getter
+ {
+ const cryptonote::transaction &tx;
+ crypto::hash lazy_txid;
+ bool computed;
+ public:
+ lazy_txid_getter(const transaction &tx): tx(tx), computed(false) {}
+ const crypto::hash &operator()()
+ {
+ if (!computed)
+ {
+ lazy_txid = cryptonote::get_transaction_hash(tx);
+ computed = true;
+ }
+ return lazy_txid;
+ }
+ } txid(tx);
+
if (!miner_tx)
process_unconfirmed(tx, height);
std::vector<size_t> outs;
@@ -199,7 +269,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_
if(!parse_tx_extra(tx.extra, tx_extra_fields))
{
// Extra may only be partially parsed, it's OK if tx_extra_fields contains public key
- LOG_PRINT_L0("Transaction extra has unsupported format: " << get_transaction_hash(tx));
+ LOG_PRINT_L0("Transaction extra has unsupported format: " << txid());
}
// Don't try to extract tx public key if tx has no ouputs
@@ -208,14 +278,19 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_
tx_extra_pub_key pub_key_field;
if(!find_tx_extra_field_by_type(tx_extra_fields, pub_key_field))
{
- LOG_PRINT_L0("Public key wasn't found in the transaction extra. Skipping transaction " << get_transaction_hash(tx));
+ LOG_PRINT_L0("Public key wasn't found in the transaction extra. Skipping transaction " << txid());
if(0 != m_callback)
m_callback->on_skip_transaction(height, tx);
return;
}
+ int num_vouts_received = 0;
tx_pub_key = pub_key_field.pub_key;
bool r = true;
+ std::deque<cryptonote::keypair> in_ephemeral(tx.vout.size());
+ std::deque<crypto::key_image> ki(tx.vout.size());
+ std::deque<uint64_t> amount(tx.vout.size());
+ std::deque<rct::key> mask(tx.vout.size());
int threads = tools::get_max_concurrency();
if (miner_tx && m_refresh_type == RefreshNoCoinbase)
{
@@ -224,8 +299,8 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_
else if (miner_tx && m_refresh_type == RefreshOptimizeCoinbase)
{
uint64_t money_transfered = 0;
- bool error = false;
- check_acc_out(m_account.get_keys(), tx.vout[0], tx_pub_key, 0, money_transfered, error);
+ bool error = false, received = false;
+ check_acc_out(m_account.get_keys(), tx.vout[0], tx_pub_key, 0, received, money_transfered, error);
if (error)
{
r = false;
@@ -233,10 +308,21 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_
else
{
// this assumes that the miner tx pays a single address
- if (money_transfered > 0)
+ if (received)
{
+ cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, 0, in_ephemeral[0], ki[0]);
+ THROW_WALLET_EXCEPTION_IF(in_ephemeral[0].pub != boost::get<cryptonote::txout_to_key>(tx.vout[0].target).key,
+ error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key");
+
outs.push_back(0);
+ if (money_transfered == 0)
+ {
+ const cryptonote::account_keys& keys = m_account.get_keys();
+ money_transfered = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, 0, mask[0]);
+ }
+ amount[0] = money_transfered;
tx_money_got_in_outs = money_transfered;
+ ++num_vouts_received;
// process the other outs from that tx
boost::asio::io_service ioservice;
@@ -250,11 +336,12 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_
const account_keys &keys = m_account.get_keys();
std::vector<uint64_t> money_transfered(tx.vout.size());
std::deque<bool> error(tx.vout.size());
+ std::deque<bool> received(tx.vout.size());
// the first one was already checked
for (size_t i = 1; i < tx.vout.size(); ++i)
{
ioservice.dispatch(boost::bind(&wallet2::check_acc_out, this, std::cref(keys), std::cref(tx.vout[i]), std::cref(tx_pub_key), i,
- std::ref(money_transfered[i]), std::ref(error[i])));
+ std::ref(received[i]), std::ref(money_transfered[i]), std::ref(error[i])));
}
KILL_IOSERVICE();
for (size_t i = 1; i < tx.vout.size(); ++i)
@@ -264,10 +351,21 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_
r = false;
break;
}
- if (money_transfered[i])
+ if (received[i])
{
+ cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]);
+ THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key,
+ error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key");
+
outs.push_back(i);
+ if (money_transfered[i] == 0)
+ {
+ const cryptonote::account_keys& keys = m_account.get_keys();
+ money_transfered[i] = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, i, mask[i]);
+ }
tx_money_got_in_outs += money_transfered[i];
+ amount[i] = money_transfered[i];
+ ++num_vouts_received;
}
}
}
@@ -286,10 +384,11 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_
const account_keys &keys = m_account.get_keys();
std::vector<uint64_t> money_transfered(tx.vout.size());
std::deque<bool> error(tx.vout.size());
+ std::deque<bool> received(tx.vout.size());
for (size_t i = 0; i < tx.vout.size(); ++i)
{
ioservice.dispatch(boost::bind(&wallet2::check_acc_out, this, std::cref(keys), std::cref(tx.vout[i]), std::cref(tx_pub_key), i,
- std::ref(money_transfered[i]), std::ref(error[i])));
+ std::ref(received[i]), std::ref(money_transfered[i]), std::ref(error[i])));
}
KILL_IOSERVICE();
tx_money_got_in_outs = 0;
@@ -300,37 +399,68 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_
r = false;
break;
}
- if (money_transfered[i])
+ if (received[i])
{
+ cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]);
+ THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key,
+ error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key");
+
outs.push_back(i);
+ if (money_transfered[i] == 0)
+ {
+ const cryptonote::account_keys& keys = m_account.get_keys();
+ money_transfered[i] = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, i, mask[i]);
+ }
tx_money_got_in_outs += money_transfered[i];
+ amount[i] = money_transfered[i];
+ ++num_vouts_received;
}
}
}
else
{
- r = lookup_acc_outs(m_account.get_keys(), tx, tx_pub_key, outs, tx_money_got_in_outs);
+ for (size_t i = 0; i < tx.vout.size(); ++i)
+ {
+ uint64_t money_transfered = 0;
+ bool error = false, received = false;
+ check_acc_out(m_account.get_keys(), tx.vout[i], tx_pub_key, i, received, money_transfered, error);
+ if (error)
+ {
+ r = false;
+ break;
+ }
+ else
+ {
+ if (received)
+ {
+ cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]);
+ THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key,
+ error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key");
+
+ outs.push_back(i);
+ if (money_transfered == 0)
+ {
+ const cryptonote::account_keys& keys = m_account.get_keys();
+ money_transfered = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, i, mask[i]);
+ }
+ amount[i] = money_transfered;
+ tx_money_got_in_outs += money_transfered;
+ ++num_vouts_received;
+ }
+ }
+ }
}
THROW_WALLET_EXCEPTION_IF(!r, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys());
- if(!outs.empty() && tx_money_got_in_outs)
+ if(!outs.empty() && num_vouts_received > 0)
{
//good news - got money! take care about it
//usually we have only one transfer for user in transaction
- cryptonote::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request req = AUTO_VAL_INIT(req);
- cryptonote::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response res = AUTO_VAL_INIT(res);
if (!pool)
{
- req.txid = get_transaction_hash(tx);
- m_daemon_rpc_mutex.lock();
- bool r = net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/get_o_indexes.bin", req, res, m_http_client, WALLET_RCP_CONNECTION_TIMEOUT);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_o_indexes.bin");
- THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_o_indexes.bin");
- THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_out_indices_error, res.status);
- THROW_WALLET_EXCEPTION_IF(res.o_indexes.size() != tx.vout.size(), error::wallet_internal_error,
- "transactions outputs size=" + std::to_string(tx.vout.size()) +
- " not match with COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES response size=" + std::to_string(res.o_indexes.size()));
+ THROW_WALLET_EXCEPTION_IF(tx.vout.size() != o_indices.size(), error::wallet_internal_error,
+ "transactions outputs size=" + std::to_string(tx.vout.size()) +
+ " not match with daemon response size=" + std::to_string(o_indices.size()));
}
BOOST_FOREACH(size_t o, outs)
@@ -338,13 +468,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_
THROW_WALLET_EXCEPTION_IF(tx.vout.size() <= o, error::wallet_internal_error, "wrong out in transaction: internal index=" +
std::to_string(o) + ", total_outs=" + std::to_string(tx.vout.size()));
- crypto::key_image ki;
- cryptonote::keypair in_ephemeral;
- cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, o, in_ephemeral, ki);
- THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != boost::get<cryptonote::txout_to_key>(tx.vout[o].target).key,
- error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key");
-
- auto kit = m_key_images.find(ki);
+ auto kit = m_key_images.find(ki[o]);
THROW_WALLET_EXCEPTION_IF(kit != m_key_images.end() && kit->second >= m_transfers.size(),
error::wallet_internal_error, std::string("Unexpected transfer index from key image: ")
+ "got " + (kit == m_key_images.end() ? "<none>" : boost::lexical_cast<std::string>(kit->second))
@@ -357,14 +481,32 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_
transfer_details& td = m_transfers.back();
td.m_block_height = height;
td.m_internal_output_index = o;
- td.m_global_output_index = res.o_indexes[o];
- td.m_tx = tx;
- td.m_key_image = ki;
- td.m_spent = false;
+ td.m_global_output_index = o_indices[o];
+ td.m_tx = (const cryptonote::transaction_prefix&)tx;
+ td.m_txid = txid();
+ td.m_key_image = ki[o];
+ td.m_amount = tx.vout[o].amount;
+ if (td.m_amount == 0)
+ {
+ td.m_mask = mask[o];
+ td.m_amount = amount[o];
+ td.m_rct = true;
+ }
+ else if (miner_tx && tx.version == 2)
+ {
+ td.m_mask = rct::identity();
+ td.m_rct = true;
+ }
+ else
+ {
+ td.m_mask = rct::identity();
+ td.m_rct = false;
+ }
+ set_unspent(td);
m_key_images[td.m_key_image] = m_transfers.size()-1;
- LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx));
+ LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid());
if (0 != m_callback)
- m_callback->on_money_received(height, td.m_tx, td.m_internal_output_index);
+ m_callback->on_money_received(height, tx, td.m_amount);
}
}
else if (m_transfers[kit->second].m_spent || m_transfers[kit->second].amount() >= tx.vout[o].amount)
@@ -387,14 +529,32 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_
transfer_details &td = m_transfers[kit->second];
td.m_block_height = height;
td.m_internal_output_index = o;
- td.m_global_output_index = res.o_indexes[o];
- td.m_tx = tx;
- THROW_WALLET_EXCEPTION_IF(td.m_key_image != ki, error::wallet_internal_error, "Inconsistent key images");
+ td.m_global_output_index = o_indices[o];
+ td.m_tx = (const cryptonote::transaction_prefix&)tx;
+ td.m_txid = txid();
+ td.m_amount = tx.vout[o].amount;
+ if (td.m_amount == 0)
+ {
+ td.m_mask = mask[o];
+ td.m_amount = amount[o];
+ td.m_rct = true;
+ }
+ else if (miner_tx && tx.version == 2)
+ {
+ td.m_mask = rct::identity();
+ td.m_rct = true;
+ }
+ else
+ {
+ td.m_mask = rct::identity();
+ td.m_rct = false;
+ }
+ THROW_WALLET_EXCEPTION_IF(td.m_key_image != ki[o], error::wallet_internal_error, "Inconsistent key images");
THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status");
- LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx));
+ LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid());
if (0 != m_callback)
- m_callback->on_money_received(height, td.m_tx, td.m_internal_output_index);
+ m_callback->on_money_received(height, tx, td.m_amount);
}
}
}
@@ -410,12 +570,20 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_
auto it = m_key_images.find(boost::get<cryptonote::txin_to_key>(in).k_image);
if(it != m_key_images.end())
{
- LOG_PRINT_L0("Spent money: " << print_money(boost::get<cryptonote::txin_to_key>(in).amount) << ", with tx: " << get_transaction_hash(tx));
- tx_money_spent_in_ins += boost::get<cryptonote::txin_to_key>(in).amount;
transfer_details& td = m_transfers[it->second];
- td.m_spent = true;
+ uint64_t amount = boost::get<cryptonote::txin_to_key>(in).amount;
+ if (amount > 0)
+ {
+ THROW_WALLET_EXCEPTION_IF(amount != td.amount(), error::wallet_internal_error,
+ std::string("Inconsistent amount in tx input: got ") + print_money(amount) +
+ std::string(", expected ") + print_money(td.amount()));
+ }
+ amount = td.amount();
+ LOG_PRINT_L0("Spent money: " << print_money(amount) << ", with tx: " << txid());
+ tx_money_spent_in_ins += amount;
+ set_spent(td, height);
if (0 != m_callback)
- m_callback->on_money_spent(height, td.m_tx, td.m_internal_output_index, tx);
+ m_callback->on_money_spent(height, tx, amount, tx);
}
}
@@ -467,7 +635,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_
}
payment_details payment;
- payment.m_tx_hash = cryptonote::get_transaction_hash(tx);
+ payment.m_tx_hash = txid();
payment.m_amount = received;
payment.m_block_height = height;
payment.m_unlock_time = tx.unlock_time;
@@ -482,6 +650,9 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_
//----------------------------------------------------------------------------------------------------
void wallet2::process_unconfirmed(const cryptonote::transaction& tx, uint64_t height)
{
+ if (m_unconfirmed_txs.empty())
+ return;
+
crypto::hash txid = get_transaction_hash(tx);
auto unconf_it = m_unconfirmed_txs.find(txid);
if(unconf_it != m_unconfirmed_txs.end()) {
@@ -501,25 +672,38 @@ void wallet2::process_unconfirmed(const cryptonote::transaction& tx, uint64_t he
void wallet2::process_outgoing(const cryptonote::transaction &tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received)
{
crypto::hash txid = get_transaction_hash(tx);
- confirmed_transfer_details &ctd = m_confirmed_txs[txid];
- // operator[] creates if not found
+ std::pair<std::unordered_map<crypto::hash, confirmed_transfer_details>::iterator, bool> entry = m_confirmed_txs.insert(std::make_pair(txid, confirmed_transfer_details()));
// fill with the info we know, some info might already be there
- ctd.m_amount_in = spent;
- ctd.m_amount_out = get_outs_money_amount(tx);
- ctd.m_change = received;
- ctd.m_block_height = height;
- ctd.m_timestamp = ts;
+ if (entry.second)
+ {
+ // this case will happen if the tx is from our outputs, but was sent by another
+ // wallet (eg, we're a cold wallet and the hot wallet sent it). For RCT transactions,
+ // we only see 0 input amounts, so have to deduce amount out from other parameters.
+ entry.first->second.m_amount_in = spent;
+ if (tx.version == 1)
+ entry.first->second.m_amount_out = get_outs_money_amount(tx);
+ else
+ entry.first->second.m_amount_out = spent - tx.rct_signatures.txnFee;
+ entry.first->second.m_change = received;
+ }
+ entry.first->second.m_block_height = height;
+ entry.first->second.m_timestamp = ts;
}
//----------------------------------------------------------------------------------------------------
-void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height)
+void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height, const cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices &o_indices)
{
+ size_t txidx = 0;
+ THROW_WALLET_EXCEPTION_IF(bche.txs.size() + 1 != o_indices.indices.size(), error::wallet_internal_error,
+ "block transactions=" + std::to_string(bche.txs.size()) +
+ " not match with daemon response size=" + std::to_string(o_indices.indices.size()));
+
//handle transactions from new block
//optimization: seeking only for blocks that are not older then the wallet creation time plus 1 day. 1 day is for possible user incorrect time setup
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(b.miner_tx, height, b.timestamp, true, false);
+ process_new_transaction(b.miner_tx, o_indices.indices[txidx++].indices, height, b.timestamp, true, false);
TIME_MEASURE_FINISH(miner_tx_handle_time);
TIME_MEASURE_START(txs_handle_time);
@@ -528,7 +712,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry
cryptonote::transaction tx;
bool r = parse_and_validate_tx_from_blob(txblob, tx);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_parse_error, txblob);
- process_new_transaction(tx, height, b.timestamp, false, false);
+ process_new_transaction(tx, o_indices.indices[txidx++].indices, height, b.timestamp, false, false);
}
TIME_MEASURE_FINISH(txs_handle_time);
LOG_PRINT_L2("Processed block: " << bl_id << ", height " << height << ", " << miner_tx_handle_time + txs_handle_time << "(" << miner_tx_handle_time << "/" << txs_handle_time <<")ms");
@@ -578,7 +762,7 @@ void wallet2::parse_block_round(const cryptonote::blobdata &blob, cryptonote::bl
bl_id = get_block_hash(bl);
}
//----------------------------------------------------------------------------------------------------
-void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::list<cryptonote::block_complete_entry> &blocks)
+void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::list<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices)
{
cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::request req = AUTO_VAL_INIT(req);
cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::response res = AUTO_VAL_INIT(res);
@@ -591,9 +775,13 @@ void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height,
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getblocks.bin");
THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getblocks.bin");
THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_blocks_error, res.status);
+ THROW_WALLET_EXCEPTION_IF(res.blocks.size() != res.output_indices.size(), error::wallet_internal_error,
+ "mismatched blocks (" + boost::lexical_cast<std::string>(res.blocks.size()) + ") and output_indices (" +
+ boost::lexical_cast<std::string>(res.output_indices.size()) + ") sizes from daemon");
blocks_start_height = res.start_height;
blocks = res.blocks;
+ o_indices = res.output_indices;
}
//----------------------------------------------------------------------------------------------------
void wallet2::pull_hashes(uint64_t start_height, uint64_t &blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::list<crypto::hash> &hashes)
@@ -614,10 +802,13 @@ void wallet2::pull_hashes(uint64_t start_height, uint64_t &blocks_start_height,
hashes = res.m_block_ids;
}
//----------------------------------------------------------------------------------------------------
-void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote::block_complete_entry> &blocks, uint64_t& blocks_added)
+void wallet2::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)
{
size_t current_index = start_height;
blocks_added = 0;
+ size_t tx_o_indices_idx = 0;
+
+ THROW_WALLET_EXCEPTION_IF(blocks.size() != o_indices.size(), error::wallet_internal_error, "size mismatch");
int threads = tools::get_max_concurrency();
if (threads > 1)
@@ -660,7 +851,7 @@ void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote::
if(current_index >= m_blockchain.size())
{
- process_new_blockchain_entry(bl, *blocki, bl_id, current_index);
+ process_new_blockchain_entry(bl, *blocki, bl_id, current_index, o_indices[b+i]);
++blocks_added;
}
else if(bl_id != m_blockchain[current_index])
@@ -672,7 +863,7 @@ void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote::
string_tools::pod_to_hex(m_blockchain[current_index]));
detach_blockchain(current_index);
- process_new_blockchain_entry(bl, *blocki, bl_id, current_index);
+ process_new_blockchain_entry(bl, *blocki, bl_id, current_index, o_indices[b+i]);
}
else
{
@@ -694,7 +885,7 @@ void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote::
crypto::hash bl_id = get_block_hash(bl);
if(current_index >= m_blockchain.size())
{
- process_new_blockchain_entry(bl, bl_entry, bl_id, current_index);
+ process_new_blockchain_entry(bl, bl_entry, bl_id, current_index, o_indices[tx_o_indices_idx]);
++blocks_added;
}
else if(bl_id != m_blockchain[current_index])
@@ -706,7 +897,7 @@ void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote::
string_tools::pod_to_hex(m_blockchain[current_index]));
detach_blockchain(current_index);
- process_new_blockchain_entry(bl, bl_entry, bl_id, current_index);
+ process_new_blockchain_entry(bl, bl_entry, bl_id, current_index, o_indices[tx_o_indices_idx]);
}
else
{
@@ -714,6 +905,7 @@ void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote::
}
++current_index;
+ ++tx_o_indices_idx;
}
}
}
@@ -730,7 +922,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched)
refresh(start_height, blocks_fetched, received_money);
}
//----------------------------------------------------------------------------------------------------
-void wallet2::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, bool &error)
+void wallet2::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)
{
error = false;
@@ -748,7 +940,7 @@ void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_hei
}
// pull the new blocks
- pull_blocks(start_height, blocks_start_height, short_chain_history, blocks);
+ pull_blocks(start_height, blocks_start_height, short_chain_history, blocks, o_indices);
}
catch(...)
{
@@ -772,7 +964,7 @@ void wallet2::update_pool_state()
std::unordered_map<crypto::hash, wallet2::unconfirmed_transfer_details>::iterator it = m_unconfirmed_txs.begin();
while (it != m_unconfirmed_txs.end())
{
- const std::string txid = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(it->second.m_tx));
+ const std::string txid = epee::string_tools::pod_to_hex(it->first);
bool found = false;
for (auto it2: res.transactions)
{
@@ -799,6 +991,24 @@ void wallet2::update_pool_state()
{
LOG_PRINT_L1("Pending txid " << txid << " not in pool, marking as failed");
pit->second.m_state = wallet2::unconfirmed_transfer_details::failed;
+
+ // the inputs aren't spent anymore, since the tx failed
+ for (size_t vini = 0; vini < pit->second.m_tx.vin.size(); ++vini)
+ {
+ 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)
+ {
+ 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);
+ break;
+ }
+ }
+ }
+ }
}
}
}
@@ -869,7 +1079,7 @@ void wallet2::update_pool_state()
{
if (tx_hash == txid)
{
- process_new_transaction(tx, 0, time(NULL), false, true);
+ process_new_transaction(tx, std::vector<uint64_t>(), 0, time(NULL), false, true);
}
else
{
@@ -980,11 +1190,12 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re
blocks_fetched = 0;
uint64_t added_blocks = 0;
size_t try_count = 0;
- crypto::hash last_tx_hash_id = m_transfers.size() ? get_transaction_hash(m_transfers.back().m_tx) : null_hash;
+ crypto::hash last_tx_hash_id = m_transfers.size() ? m_transfers.back().m_txid : null_hash;
std::list<crypto::hash> short_chain_history;
boost::thread pull_thread;
uint64_t blocks_start_height;
std::list<cryptonote::block_complete_entry> blocks;
+ std::vector<COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> o_indices;
// pull the first set of blocks
get_short_chain_history(short_chain_history);
@@ -1001,7 +1212,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re
// and then fall through to regular refresh processing
}
- pull_blocks(start_height, blocks_start_height, short_chain_history, blocks);
+ pull_blocks(start_height, blocks_start_height, short_chain_history, blocks, o_indices);
// always reset start_height to 0 to force short_chain_ history to be used on
// subsequent pulls in this refresh.
start_height = 0;
@@ -1013,10 +1224,11 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re
// pull the next set of blocks while we're processing the current one
uint64_t next_blocks_start_height;
std::list<cryptonote::block_complete_entry> next_blocks;
+ std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> next_o_indices;
bool error = false;
- pull_thread = boost::thread([&]{pull_next_blocks(start_height, next_blocks_start_height, short_chain_history, blocks, next_blocks, error);});
+ pull_thread = boost::thread([&]{pull_next_blocks(start_height, next_blocks_start_height, short_chain_history, blocks, next_blocks, next_o_indices, error);});
- process_blocks(blocks_start_height, blocks, added_blocks);
+ process_blocks(blocks_start_height, blocks, o_indices, added_blocks);
blocks_fetched += added_blocks;
pull_thread.join();
if(!added_blocks)
@@ -1025,6 +1237,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re
// switch to the new blocks from the daemon
blocks_start_height = next_blocks_start_height;
blocks = next_blocks;
+ o_indices = next_o_indices;
// handle error from async fetching thread
if (error)
@@ -1049,7 +1262,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re
}
}
}
- if(last_tx_hash_id != (m_transfers.size() ? get_transaction_hash(m_transfers.back().m_tx) : null_hash))
+ if(last_tx_hash_id != (m_transfers.size() ? m_transfers.back().m_txid : null_hash))
received_money = true;
try
@@ -1083,6 +1296,16 @@ void wallet2::detach_blockchain(uint64_t height)
LOG_PRINT_L0("Detaching blockchain on height " << height);
size_t transfers_detached = 0;
+ for (size_t i = 0; i < m_transfers.size(); ++i)
+ {
+ wallet2::transfer_details &td = m_transfers[i];
+ 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);
+ }
+ }
+
auto it = std::find_if(m_transfers.begin(), m_transfers.end(), [&](const transfer_details& td){return td.m_block_height >= height;});
size_t i_start = it - m_transfers.begin();
@@ -1272,26 +1495,25 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa
const char *field_key_data = json["key_data"].GetString();
account_data = std::string(field_key_data, field_key_data + json["key_data"].GetStringLength());
- GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed_language, std::string, String, false);
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed_language, std::string, String, false, std::string());
if (field_seed_language_found)
{
set_seed_language(field_seed_language);
}
- GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, watch_only, int, Int, false);
- m_watch_only = field_watch_only_found && field_watch_only;
- GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false);
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, watch_only, int, Int, false, false);
+ m_watch_only = field_watch_only;
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false, false);
m_always_confirm_transfers = field_always_confirm_transfers_found && field_always_confirm_transfers;
- GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_keys, int, Int, false);
- GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_info, int, Int, false);
- m_store_tx_info = (field_store_tx_keys_found && (field_store_tx_keys != 0))
- || (field_store_tx_info_found && (field_store_tx_info != 0));
- GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_mixin, unsigned int, Uint, false);
- m_default_mixin = field_default_mixin_found ? field_default_mixin : 0;
- GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_fee_multiplier, unsigned int, Uint, false);
- m_default_fee_multiplier = field_default_fee_multiplier_found ? field_default_fee_multiplier : 0;
- GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, auto_refresh, int, Int, false);
- m_auto_refresh = !field_auto_refresh_found || (field_auto_refresh != 0);
- GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, refresh_type, int, Int, false);
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_keys, int, Int, false, true);
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_info, int, Int, false, true);
+ m_store_tx_info = ((field_store_tx_keys != 0) || (field_store_tx_info != 0));
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_mixin, unsigned int, Uint, false, 0);
+ m_default_mixin = field_default_mixin;
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_fee_multiplier, unsigned int, Uint, false, 0);
+ m_default_fee_multiplier = field_default_fee_multiplier;
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, auto_refresh, int, Int, false, true);
+ m_auto_refresh = field_auto_refresh;
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, refresh_type, int, Int, false, RefreshType::RefreshDefault);
m_refresh_type = RefreshType::RefreshDefault;
if (field_refresh_type_found)
{
@@ -1300,7 +1522,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa
else
LOG_PRINT_L0("Unknown refresh-type value (" << field_refresh_type << "), using default");
}
- GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, refresh_height, uint64_t, Uint64, false);
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, refresh_height, uint64_t, Uint64, false, 0);
if (field_refresh_height_found)
m_refresh_from_block_height = field_refresh_height;
}
@@ -1896,12 +2118,15 @@ 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);
+ 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);
+ // unknown height, if this gets reorged, it might still be missed
}
- td.m_spent = daemon_resp.spent_status[i] != COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT;
}
}
}
@@ -1962,6 +2187,7 @@ namespace
T pop_index(std::vector<T>& vec, size_t idx)
{
CHECK_AND_ASSERT_MES(!vec.empty(), T(), "Vector must be non-empty");
+ CHECK_AND_ASSERT_MES(idx < vec.size(), T(), "idx out of bounds");
T res = vec[idx];
if (idx + 1 != vec.size())
@@ -1981,6 +2207,84 @@ namespace
size_t idx = crypto::rand<size_t>() % vec.size();
return pop_index (vec, idx);
}
+
+ template<typename T>
+ T pop_back(std::vector<T>& vec)
+ {
+ CHECK_AND_ASSERT_MES(!vec.empty(), T(), "Vector must be non-empty");
+
+ T res = vec.back();
+ vec.pop_back();
+ return res;
+ }
+}
+//----------------------------------------------------------------------------------------------------
+// This returns a handwavy estimation of how much two outputs are related
+// If they're from the same tx, then they're fully related. From close block
+// heights, they're kinda related. The actual values don't matter, just
+// their ordering, but it could become more murky if we add scores later.
+float wallet2::get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const
+{
+ int dh;
+
+ // expensive test, and same tx will fall onto the same block height below
+ if (td0.m_txid == td1.m_txid)
+ return 1.0f;
+
+ // same block height -> possibly tx burst, or same tx (since above is disabled)
+ dh = td0.m_block_height > td1.m_block_height ? td0.m_block_height - td1.m_block_height : td1.m_block_height - td0.m_block_height;
+ if (dh == 0)
+ return 0.9f;
+
+ // adjacent blocks -> possibly tx burst
+ if (dh == 1)
+ return 0.8f;
+
+ // could extract the payment id, and compare them, but this is a bit expensive too
+
+ // similar block heights
+ if (dh < 10)
+ return 0.2f;
+
+ // don't think these are particularly related
+ 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
+{
+ std::vector<size_t> candidates;
+ float best_relatedness = 1.0f;
+ for (size_t n = 0; n < unused_indices.size(); ++n)
+ {
+ const transfer_details &candidate = transfers[unused_indices[n]];
+ float relatedness = 0.0f;
+ for (const auto &i: selected_transfers)
+ {
+ float r = get_output_relatedness(candidate, *i);
+ if (r > relatedness)
+ {
+ relatedness = r;
+ if (relatedness == 1.0f)
+ break;
+ }
+ }
+
+ if (relatedness < best_relatedness)
+ {
+ best_relatedness = relatedness;
+ candidates.clear();
+ }
+
+ if (relatedness == best_relatedness)
+ candidates.push_back(n);
+ }
+ size_t idx = crypto::rand<size_t>() % candidates.size();
+ 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
+{
+ return pop_best_value_from(m_transfers, unused_indices, selected_transfers);
}
//----------------------------------------------------------------------------------------------------
// Select random input sources for transaction.
@@ -1992,7 +2296,7 @@ uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> un
uint64_t found_money = 0;
while (found_money < needed_money && !unused_transfers_indices.empty())
{
- size_t idx = pop_random_value(unused_transfers_indices);
+ size_t idx = pop_best_value(unused_transfers_indices, selected_transfers);
transfer_container::iterator it = m_transfers.begin() + idx;
selected_transfers.push_back(it);
@@ -2002,12 +2306,16 @@ uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> un
return found_money;
}
//----------------------------------------------------------------------------------------------------
-void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, const std::vector<cryptonote::tx_destination_entry> &dests, const crypto::hash &payment_id, uint64_t change_amount)
+void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amount_in, const std::vector<cryptonote::tx_destination_entry> &dests, const crypto::hash &payment_id, uint64_t change_amount)
{
unconfirmed_transfer_details& utd = m_unconfirmed_txs[cryptonote::get_transaction_hash(tx)];
+ utd.m_amount_in = amount_in;
+ utd.m_amount_out = 0;
+ for (const auto &d: dests)
+ utd.m_amount_out += d.amount;
utd.m_change = change_amount;
utd.m_sent_time = time(NULL);
- utd.m_tx = tx;
+ utd.m_tx = (const cryptonote::transaction_prefix&)tx;
utd.m_dests = dests;
utd.m_payment_id = payment_id;
utd.m_state = wallet2::unconfirmed_transfer_details::pending;
@@ -2190,19 +2498,26 @@ void wallet2::commit_tx(pending_tx& ptx)
txid = get_transaction_hash(ptx.tx);
crypto::hash payment_id = cryptonote::null_hash;
std::vector<cryptonote::tx_destination_entry> dests;
+ uint64_t amount_in = 0;
if (store_tx_info())
{
payment_id = get_payment_id(ptx);
dests = ptx.dests;
+ BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers)
+ amount_in += it->amount();
}
- add_unconfirmed_tx(ptx.tx, dests, payment_id, ptx.change_dts.amount);
+ add_unconfirmed_tx(ptx.tx, amount_in, dests, payment_id, ptx.change_dts.amount);
if (store_tx_info())
+ {
m_tx_keys.insert(std::make_pair(txid, ptx.tx_key));
+ }
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)
- it->m_spent = true;
+ {
+ set_spent(*it, 0);
+ }
//fee includes dust if dust policy specified it.
LOG_PRINT_L0("Transaction successfully sent. <" << txid << ">" << ENDL
@@ -2280,7 +2595,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto
// mark transfers to be used as "spent"
BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers)
- it->m_spent = true;
+ {
+ set_spent(*it, 0);
+ }
}
// if we made it this far, we've selected our transactions. committing them will mark them spent,
@@ -2290,7 +2607,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto
{
// mark transfers to be used as not spent
BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers)
- it2->m_spent = false;
+ {
+ set_unspent(*it2);
+ }
}
@@ -2307,8 +2626,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto
{
// mark transfers to be used as not spent
BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers)
- it2->m_spent = false;
-
+ {
+ set_unspent(*it2);
+ }
}
if (attempt_count >= MAX_SPLIT_ATTEMPTS)
@@ -2325,8 +2645,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto
{
// mark transfers to be used as not spent
BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers)
- it2->m_spent = false;
-
+ {
+ set_unspent(*it2);
+ }
}
throw;
@@ -2334,40 +2655,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto
}
}
-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,
- 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)
+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)
{
- using namespace cryptonote;
- // throw if attempting a transaction with no destinations
- THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination);
-
- uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit();
- uint64_t needed_money = fee;
- LOG_PRINT_L2("transfer: starting with fee " << print_money (needed_money));
-
- // calculate total amount being sent to all destinations
- // throw if total amount overflows uint64_t
- BOOST_FOREACH(auto& dt, dsts)
- {
- THROW_WALLET_EXCEPTION_IF(0 == dt.amount, error::zero_destination);
- needed_money += dt.amount;
- LOG_PRINT_L2("transfer: adding " << print_money(dt.amount) << ", for a total of " << print_money (needed_money));
- THROW_WALLET_EXCEPTION_IF(needed_money < dt.amount, error::tx_sum_overflow, dsts, fee, m_testnet);
- }
-
- uint64_t found_money = 0;
- BOOST_FOREACH(auto it, selected_transfers)
- {
- found_money += it->amount();
- }
-
- 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);
-
- typedef std::pair<uint64_t, crypto::public_key> entry;
- std::vector<std::vector<entry>> outs;
- if (fake_outputs_count)
+ LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count);
+ outs.clear();
+ if (fake_outputs_count > 0)
{
// get histogram for the amounts we need
epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request> req_t = AUTO_VAL_INIT(req_t);
@@ -2377,7 +2670,10 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
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->amount());
+ req_t.params.amounts.push_back(it->is_rct() ? 0 : it->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));
req_t.params.unlocked = true;
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();
@@ -2387,12 +2683,15 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
// we ask for more, to have spares if some outputs are still locked
size_t requested_outputs_count = (size_t)((fake_outputs_count + 1) * 1.5 + 1);
+ LOG_PRINT_L2("requested_outputs_count: " << requested_outputs_count);
// generate output indices to request
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)
{
+ const uint64_t amount = it->is_rct() ? 0 : it->amount();
std::unordered_set<uint64_t> seen_indices;
size_t start = req.outputs.size();
@@ -2401,28 +2700,30 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
uint64_t num_outs = 0;
for (auto he: resp_t.result.histogram)
{
- if (he.amount == it->amount())
+ if (he.amount == amount)
{
num_outs = he.instances;
break;
}
}
+ LOG_PRINT_L1("" << num_outs << " outputs of size " << print_money(amount));
+
if (num_outs <= requested_outputs_count)
{
for (uint64_t i = 0; i < num_outs; i++)
- req.outputs.push_back({it->amount(), i});
+ req.outputs.push_back({amount, i});
// duplicate to make up shortfall: this will be caught after the RPC call,
// so we can also output the amounts for which we can't reach the required
// mixin after checking the actual unlockedness
for (uint64_t i = num_outs; i < requested_outputs_count; ++i)
- req.outputs.push_back({it->amount(), num_outs - 1});
+ req.outputs.push_back({amount, num_outs - 1});
}
else
{
// start with real one
uint64_t num_found = 1;
seen_indices.emplace(it->m_global_output_index);
- req.outputs.push_back({it->amount(), it->m_global_output_index});
+ req.outputs.push_back({amount, it->m_global_output_index});
// while we still need more mixins
while (num_found < requested_outputs_count)
@@ -2447,7 +2748,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
continue;
seen_indices.emplace(i);
- req.outputs.push_back({it->amount(), i});
+ req.outputs.push_back({amount, i});
++num_found;
}
}
@@ -2457,6 +2758,9 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
[](const COMMAND_RPC_GET_OUTPUTS::out &a, const COMMAND_RPC_GET_OUTPUTS::out &b) { return a.index < b.index; });
}
+ for (auto i: req.outputs)
+ LOG_PRINT_L1("asking for output " << i.index << " for " << print_money(i.amount));
+
// get the keys for those
m_daemon_rpc_mutex.lock();
r = epee::net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/get_outs.bin", req, daemon_resp, m_http_client, 200000);
@@ -2475,9 +2779,10 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
{
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());
// pick real out first (it will be sorted when done)
- outs.back().push_back({it->m_global_output_index, boost::get<txout_to_key>(it->m_tx.vout[it->m_internal_output_index].target).key});
+ 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));
// 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
@@ -2496,7 +2801,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
continue;
if (o > 0 && daemon_resp.outs[i].key == daemon_resp.outs[i - 1].key) // don't add duplicates
continue;
- outs.back().push_back({req.outputs[i].index, daemon_resp.outs[i].key});
+ outs.back().push_back(std::make_tuple(req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask));
}
if (outs.back().size() < fake_outputs_count + 1)
{
@@ -2505,14 +2810,14 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
else
{
// sort the subsection, so any spares are reset in order
- std::sort(outs.back().begin(), outs.back().end(), [](const entry &a, const entry &b) { return a.first < b.first; });
+ std::sort(outs.back().begin(), outs.back().end(), [](const entry &a, const entry &b) { return std::get<0>(a) < std::get<0>(b); });
// sanity check
for (size_t n = 1; n < outs.back().size(); ++n)
{
- THROW_WALLET_EXCEPTION_IF(outs.back()[n].first == outs.back()[n-1].first, error::wallet_internal_error,
+ THROW_WALLET_EXCEPTION_IF(std::get<0>(outs.back()[n]) == std::get<0>(outs.back()[n-1]), error::wallet_internal_error,
"Duplicate indices though we did not ask for any");
- THROW_WALLET_EXCEPTION_IF(outs.back()[n].second == outs.back()[n-1].second, error::wallet_internal_error,
+ THROW_WALLET_EXCEPTION_IF(std::get<1>(outs.back()[n]) == std::get<1>(outs.back()[n-1]), error::wallet_internal_error,
"Duplicate keys after we have weeded them out");
}
}
@@ -2525,10 +2830,47 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
for (transfer_container::iterator it: selected_transfers)
{
std::vector<entry> v;
- v.push_back({it->m_global_output_index, boost::get<txout_to_key>(it->m_tx.vout[it->m_internal_output_index].target).key});
+ 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));
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,
+ 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;
+ // throw if attempting a transaction with no destinations
+ THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination);
+
+ uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit();
+ uint64_t needed_money = fee;
+ LOG_PRINT_L2("transfer: starting with fee " << print_money (needed_money));
+
+ // calculate total amount being sent to all destinations
+ // throw if total amount overflows uint64_t
+ BOOST_FOREACH(auto& dt, dsts)
+ {
+ THROW_WALLET_EXCEPTION_IF(0 == dt.amount, error::zero_destination);
+ needed_money += dt.amount;
+ LOG_PRINT_L2("transfer: adding " << print_money(dt.amount) << ", for a total of " << print_money (needed_money));
+ THROW_WALLET_EXCEPTION_IF(needed_money < dt.amount, error::tx_sum_overflow, dsts, fee, m_testnet);
+ }
+
+ uint64_t found_money = 0;
+ BOOST_FOREACH(auto it, selected_transfers)
+ {
+ found_money += it->amount();
+ }
+
+ 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);
+
+ typedef std::tuple<uint64_t, crypto::public_key, rct::key> entry;
+ std::vector<std::vector<entry>> outs;
+ get_outs(outs, selected_transfers, fake_outputs_count); // may throw
//prepare inputs
typedef cryptonote::tx_source_entry::output_entry tx_output_entry;
@@ -2540,13 +2882,16 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
cryptonote::tx_source_entry& src = sources.back();
transfer_details& td = *it;
src.amount = td.amount();
+ src.rct = td.is_rct();
//paste keys (fake and real)
for (size_t n = 0; n < fake_outputs_count + 1; ++n)
{
tx_output_entry oe;
- oe.first = outs[out_index][n].first;
- oe.second = outs[out_index][n].second;
+ oe.first = std::get<0>(outs[out_index][n]);
+ oe.second.dest = rct::pk2rct(std::get<1>(outs[out_index][n]));
+ oe.second.mask = std::get<2>(outs[out_index][n]);
+
src.outputs.push_back(oe);
++i;
}
@@ -2561,7 +2906,8 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
tx_output_entry real_oe;
real_oe.first = td.m_global_output_index;
- real_oe.second = boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key;
+ real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key);
+ real_oe.second.mask = rct::commit(td.amount(), td.m_mask);
*it_to_replace = real_oe;
src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx);
src.real_output = it_to_replace - src.outputs.begin();
@@ -2621,6 +2967,225 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
ptx.dests = dsts;
}
+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,
+ uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx)
+{
+ using namespace cryptonote;
+ // throw if attempting a transaction with no destinations
+ THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination);
+
+ uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit();
+ uint64_t needed_money = fee;
+ LOG_PRINT_L2("transfer: starting with fee " << print_money (needed_money));
+
+ // calculate total amount being sent to all destinations
+ // throw if total amount overflows uint64_t
+ BOOST_FOREACH(auto& dt, dsts)
+ {
+ THROW_WALLET_EXCEPTION_IF(0 == dt.amount, error::zero_destination);
+ needed_money += dt.amount;
+ LOG_PRINT_L2("transfer: adding " << print_money(dt.amount) << ", for a total of " << print_money (needed_money));
+ THROW_WALLET_EXCEPTION_IF(needed_money < dt.amount, error::tx_sum_overflow, dsts, fee, m_testnet);
+ }
+
+ uint64_t found_money = 0;
+ BOOST_FOREACH(auto it, selected_transfers)
+ {
+ found_money += it->amount();
+ }
+
+ 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);
+
+ typedef std::tuple<uint64_t, crypto::public_key, rct::key> entry;
+ std::vector<std::vector<entry>> outs;
+ get_outs(outs, selected_transfers, fake_outputs_count); // may throw
+
+ //prepare inputs
+ size_t i = 0, out_index = 0;
+ std::vector<cryptonote::tx_source_entry> sources;
+ BOOST_FOREACH(transfer_container::iterator it, selected_transfers)
+ {
+ sources.resize(sources.size()+1);
+ cryptonote::tx_source_entry& src = sources.back();
+ transfer_details& td = *it;
+ src.amount = td.amount();
+ src.rct = td.is_rct();
+ //paste mixin transaction
+
+ typedef cryptonote::tx_source_entry::output_entry tx_output_entry;
+ for (size_t n = 0; n < fake_outputs_count + 1; ++n)
+ {
+ tx_output_entry oe;
+ oe.first = std::get<0>(outs[out_index][n]);
+ oe.second.dest = rct::pk2rct(std::get<1>(outs[out_index][n]));
+ oe.second.mask = std::get<2>(outs[out_index][n]);
+ src.outputs.push_back(oe);
+ }
+ ++i;
+
+ //paste real transaction to the random index
+ auto it_to_replace = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a)
+ {
+ return a.first == td.m_global_output_index;
+ });
+ THROW_WALLET_EXCEPTION_IF(it_to_replace == src.outputs.end(), error::wallet_internal_error,
+ "real output not found");
+
+ tx_output_entry real_oe;
+ real_oe.first = td.m_global_output_index;
+ real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key);
+ real_oe.second.mask = rct::commit(td.amount(), td.m_mask);
+ *it_to_replace = real_oe;
+ src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx);
+ src.real_output = it_to_replace - src.outputs.begin();
+ src.real_output_in_tx_index = td.m_internal_output_index;
+ src.mask = td.m_mask;
+ detail::print_source_entry(src);
+ ++out_index;
+ }
+
+ cryptonote::tx_destination_entry change_dts = AUTO_VAL_INIT(change_dts);
+ if (needed_money < found_money)
+ {
+ change_dts.addr = m_account.get_keys().m_account_address;
+ change_dts.amount = found_money - needed_money;
+ dsts.push_back(change_dts);
+ }
+
+ crypto::secret_key tx_key;
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sources, dsts, extra, tx, unlock_time, tx_key, true);
+ THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_testnet);
+ THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit);
+
+ std::string key_images;
+ bool all_are_txin_to_key = std::all_of(tx.vin.begin(), 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, tx);
+
+ ptx.key_images = key_images;
+ ptx.fee = fee;
+ ptx.dust = 0;
+ ptx.dust_added_to_fee = false;
+ ptx.tx = tx;
+ ptx.change_dts = change_dts;
+ ptx.selected_transfers = selected_transfers;
+ ptx.tx_key = tx_key;
+ ptx.dests = dsts;
+}
+
+static size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs)
+{
+ size_t size = 0;
+
+ // tx prefix
+
+ // first few bytes
+ size += 1 + 6;
+
+ // vin
+ size += n_inputs * (1+6+(mixin+1)*2+32);
+
+ // vout
+ size += n_outputs * (6+32);
+
+ // extra
+ size += 40;
+
+ // rct signatures
+
+ // simple
+ size += 1;
+
+ // message
+ size += 32;
+
+ // rangeSigs
+ size += (2*64*32+32+64*32) * n_outputs;
+
+ // MGs - only the last slot of II is saved, the rest can be reconstructed
+ size += n_inputs * (32 * (mixin+1) * n_inputs + 32 + 32 * (/*n_inputs+*/1));
+
+ // mixRing - not serialized, can be reconstructed
+ /* size += 2 * 32 * (mixin+1) * n_inputs; */
+
+ // pseudoOuts
+ size += 32 * n_outputs;
+ // ecdhInfo
+ size += 3 * 32 * n_outputs;
+ // outPk - only commitment is saved
+ size += 1 * 32 * n_outputs;
+ // txnFee
+ size += 4;
+
+ LOG_PRINT_L2("estimated rct tx size for " << n_inputs << " at mixin " << mixin << " and " << n_outputs << ": " << size << " (" << ((32 * n_inputs/*+1*/) + 2 * 32 * (mixin+1) * n_inputs + 32 * n_outputs) << " saved)");
+ return size;
+}
+
+std::vector<size_t> wallet2::pick_prefered_rct_inputs(uint64_t needed_money) const
+{
+ std::vector<size_t> picks;
+ float current_output_relatdness = 1.0f;
+
+ LOG_PRINT_L2("pick_prefered_rct_inputs: needed_money " << print_money(needed_money));
+
+ // try to find a rct input of enough size
+ for (size_t i = 0; i < m_transfers.size(); ++i)
+ {
+ const transfer_details& td = m_transfers[i];
+ if (!td.m_spent && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td))
+ {
+ LOG_PRINT_L2("We can use " << i << " alone: " << print_money(td.amount()));
+ picks.push_back(i);
+ return picks;
+ }
+ }
+
+ // then try to find two outputs
+ // this could be made better by picking one of the outputs to be a small one, since those
+ // are less useful since often below the needed money, so if one can be used in a pair,
+ // it gets rid of it for the future
+ for (size_t i = 0; i < m_transfers.size(); ++i)
+ {
+ const transfer_details& td = m_transfers[i];
+ if (!td.m_spent && td.is_rct() && is_transfer_unlocked(td))
+ {
+ LOG_PRINT_L2("Considering input " << i << ", " << print_money(td.amount()));
+ for (size_t j = i + 1; j < m_transfers.size(); ++j)
+ {
+ const transfer_details& td2 = m_transfers[j];
+ if (!td2.m_spent && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2))
+ {
+ // update our picks if those outputs are less related than any we
+ // already found. If the same, don't update, and oldest suitable outputs
+ // will be used in preference.
+ float relatedness = get_output_relatedness(td, td2);
+ LOG_PRINT_L2(" with input " << j << ", " << print_money(td2.amount()) << ", relatedness " << relatedness);
+ if (relatedness < current_output_relatdness)
+ {
+ // reset the current picks with those, and return them directly
+ // if they're unrelated. If they are related, we'll end up returning
+ // them if we find nothing better
+ picks.clear();
+ picks.push_back(i);
+ picks.push_back(j);
+ LOG_PRINT_L0("we could use " << i << " and " << j);
+ if (relatedness == 0.0f)
+ return picks;
+ current_output_relatdness = relatedness;
+ }
+ }
+ }
+ }
+ }
+
+ return picks;
+}
+
// Another implementation of transaction creation that is hopefully better
// While there is anything left to pay, it goes through random outputs and tries
// to fill the next destination/amount. If it fully fills it, it will use the
@@ -2662,12 +3227,13 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
bool adding_fee; // true if new outputs go towards fee, rather than destinations
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);
+
+ fee_multiplier = sanitize_fee_multiplier(fee_multiplier);
// throw if attempting a transaction with no destinations
THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination);
- fee_multiplier = sanitize_fee_multiplier (fee_multiplier);
-
// calculate total amount being sent to all destinations
// throw if total amount overflows uint64_t
needed_money = 0;
@@ -2686,9 +3252,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
- if (!td.m_spent && is_transfer_unlocked(td))
+ if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td))
{
- if (is_valid_decomposed_amount(td.amount()))
+ if ((td.is_rct()) || is_valid_decomposed_amount(td.amount()))
unused_transfers_indices.push_back(i);
else
unused_dust_indices.push_back(i);
@@ -2704,6 +3270,28 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
adding_fee = false;
needed_fee = 0;
+ // for rct, since we don't see the amounts, we will try to make all transactions
+ // look the same, with 1 or 2 inputs, and 2 outputs. One input is preferable, as
+ // this prevents linking to another by provenance analysis, but two is ok if we
+ // try to pick outputs not from the same block. We will get two outputs, one for
+ // the destination, and one for change.
+ std::vector<size_t> prefered_inputs;
+ uint64_t rct_outs_needed = 2 * (fake_outs_count + 1);
+ rct_outs_needed += 100; // some fudge factor since we don't know how many are locked
+ if (use_rct && get_num_rct_outputs() >= rct_outs_needed)
+ {
+ // this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which
+ // will get us a known fee.
+ uint64_t estimated_fee = calculate_fee(estimate_rct_tx_size(2, fake_outs_count + 1, 2), fee_multiplier);
+ prefered_inputs = pick_prefered_rct_inputs(needed_money + estimated_fee);
+ if (!prefered_inputs.empty())
+ {
+ string s;
+ for (auto i: prefered_inputs) s += print_money(m_transfers[i].amount()) + " ";
+ LOG_PRINT_L1("Found prefered rct inputs for rct tx: " << s);
+ }
+ }
+
// while we have something to send
while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee) {
TX &tx = txes.back();
@@ -2716,7 +3304,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// get a random unspent output and use it to pay part (or all) of the current destination (and maybe next one, etc)
// This could be more clever, but maybe at the cost of making probabilistic inferences easier
- size_t idx = !unused_transfers_indices.empty() ? pop_random_value(unused_transfers_indices) : pop_random_value(unused_dust_indices);
+ size_t idx = !prefered_inputs.empty() ? pop_back(prefered_inputs) : !unused_transfers_indices.empty() ? pop_best_value(unused_transfers_indices, tx.selected_transfers) : pop_best_value(unused_dust_indices, tx.selected_transfers);
const transfer_details &td = m_transfers[idx];
LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount()));
@@ -2765,7 +3353,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
}
else
{
- try_tx = dsts.empty() || (tx.selected_transfers.size() * (fake_outs_count+1) * APPROXIMATE_INPUT_BYTES >= TX_SIZE_TARGET(upper_transaction_size_limit));
+ size_t estimated_rct_tx_size;
+ if (use_rct)
+ estimated_rct_tx_size = estimate_rct_tx_size(tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 1);
+ else
+ estimated_rct_tx_size = tx.selected_transfers.size() * (fake_outs_count+1) * APPROXIMATE_INPUT_BYTES;
+ try_tx = dsts.empty() || (estimated_rct_tx_size >= TX_SIZE_TARGET(upper_transaction_size_limit));
}
if (try_tx) {
@@ -2776,8 +3369,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " <<
tx.selected_transfers.size() << " outputs");
- transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
- detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
+ if (use_rct)
+ transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
+ test_tx, test_ptx);
+ else
+ transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
+ detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
auto txBlob = t_serializable_object_to_blob(test_ptx.tx);
needed_fee = calculate_fee(txBlob, fee_multiplier);
available_for_fee = test_ptx.fee + test_ptx.change_dts.amount + (!test_ptx.dust_added_to_fee ? test_ptx.dust : 0);
@@ -2814,11 +3411,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
else
{
LOG_PRINT_L2("We made a tx, adjusting fee and saving it");
- transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
- detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
+ if (use_rct)
+ transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
+ test_tx, test_ptx);
+ else
+ transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
+ detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
txBlob = t_serializable_object_to_blob(test_ptx.tx);
- LOG_PRINT_L2("Made a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) <<
- " fee and " << print_money(test_ptx.change_dts.amount) << " change");
+ LOG_PRINT_L2("Made a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) <<
+ " fee and " << print_money(test_ptx.change_dts.amount) << " change");
tx.tx = test_tx;
tx.ptx = test_ptx;
@@ -2862,7 +3463,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
return ptx_vector;
}
-std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, uint64_t fee_multiplier, const std::vector<uint8_t> extra, bool trusted_daemon)
+std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee_multiplier, const std::vector<uint8_t> extra, bool trusted_daemon)
{
std::vector<size_t> unused_transfers_indices;
std::vector<size_t> unused_dust_indices;
@@ -2877,16 +3478,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono
std::vector<TX> txes;
uint64_t needed_fee, available_for_fee = 0;
uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit();
-
- fee_multiplier = sanitize_fee_multiplier(fee_multiplier);
+ const bool use_rct = use_fork_rules(4, 0);
// gather all our dust and non dust outputs
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
- if (!td.m_spent && is_transfer_unlocked(td))
+ if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td))
{
- if (is_valid_decomposed_amount(td.amount()))
+ if (td.is_rct() || is_valid_decomposed_amount(td.amount()))
unused_transfers_indices.push_back(i);
else
unused_dust_indices.push_back(i);
@@ -2908,7 +3508,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono
// get a random unspent output and use it to pay next chunk. We try to alternate
// dust and non dust to ensure we never get with only dust, from which we might
// get a tx that can't pay for itself
- size_t idx = unused_transfers_indices.empty() ? pop_random_value(unused_dust_indices) : unused_dust_indices.empty() ? pop_random_value(unused_transfers_indices) : ((tx.selected_transfers.size() & 1) || accumulated_outputs > FEE_PER_KB * fee_multiplier * (upper_transaction_size_limit + 1023) / 1024) ? pop_random_value(unused_dust_indices) : pop_random_value(unused_transfers_indices);
+ size_t idx = unused_transfers_indices.empty() ? pop_best_value(unused_dust_indices, tx.selected_transfers) : unused_dust_indices.empty() ? pop_best_value(unused_transfers_indices, tx.selected_transfers) : ((tx.selected_transfers.size() & 1) || accumulated_outputs > FEE_PER_KB * fee_multiplier * (upper_transaction_size_limit + 1023) / 1024) ? pop_best_value(unused_dust_indices, tx.selected_transfers) : pop_best_value(unused_transfers_indices, tx.selected_transfers);
const transfer_details &td = m_transfers[idx];
LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount()));
@@ -2921,7 +3521,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono
// here, check if we need to sent tx and start a new one
LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit "
<< upper_transaction_size_limit);
- bool try_tx = (unused_dust_indices.empty() && unused_transfers_indices.empty()) || (tx.selected_transfers.size() * (fake_outs_count+1) * APPROXIMATE_INPUT_BYTES >= TX_SIZE_TARGET(upper_transaction_size_limit));
+ size_t estimated_rct_tx_size;
+ if (use_rct)
+ estimated_rct_tx_size = estimate_rct_tx_size(tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 1);
+ else
+ estimated_rct_tx_size = tx.selected_transfers.size() * (fake_outs_count+1) * APPROXIMATE_INPUT_BYTES;
+ bool try_tx = (unused_dust_indices.empty() && unused_transfers_indices.empty()) || ( estimated_rct_tx_size >= TX_SIZE_TARGET(upper_transaction_size_limit));
if (try_tx) {
cryptonote::transaction test_tx;
@@ -2933,8 +3538,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono
LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " <<
tx.selected_transfers.size() << " outputs");
- transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
- detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
+ if (use_rct)
+ transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
+ test_tx, test_ptx);
+ else
+ transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
+ detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
auto txBlob = t_serializable_object_to_blob(test_ptx.tx);
needed_fee = calculate_fee(txBlob, fee_multiplier);
available_for_fee = test_ptx.fee + test_ptx.dests[0].amount + test_ptx.change_dts.amount;
@@ -2946,8 +3555,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono
do {
LOG_PRINT_L2("We made a tx, adjusting fee and saving it");
tx.dsts[0].amount = available_for_fee - needed_fee;
- transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
- detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
+ if (use_rct)
+ transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
+ test_tx, test_ptx);
+ else
+ transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
+ detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
txBlob = t_serializable_object_to_blob(test_ptx.tx);
needed_fee = calculate_fee(txBlob, fee_multiplier);
LOG_PRINT_L2("Made an attempt at a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) <<
@@ -3057,6 +3670,7 @@ void wallet2::transfer_from(const std::vector<size_t> &outs, size_t num_outputs,
cryptonote::tx_source_entry& src = sources.back();
transfer_details& td = *it;
src.amount = td.amount();
+ src.rct = td.is_rct();
//paste real transaction to the random index
auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a)
@@ -3065,7 +3679,8 @@ void wallet2::transfer_from(const std::vector<size_t> &outs, size_t num_outputs,
});
tx_output_entry real_oe;
real_oe.first = td.m_global_output_index;
- real_oe.second = boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key;
+ real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key);
+ real_oe.second.mask = rct::commit(td.amount(), td.m_mask);
auto interted_it = src.outputs.insert(it_to_insert, real_oe);
src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx);
src.real_output = interted_it - src.outputs.begin();
@@ -3113,36 +3728,45 @@ void wallet2::transfer_from(const std::vector<size_t> &outs, size_t num_outputs,
}
//----------------------------------------------------------------------------------------------------
-bool wallet2::use_fork_rules(uint8_t version)
+void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height)
{
- cryptonote::COMMAND_RPC_GET_HEIGHT::request req = AUTO_VAL_INIT(req);
- cryptonote::COMMAND_RPC_GET_HEIGHT::response res = AUTO_VAL_INIT(res);
epee::json_rpc::request<cryptonote::COMMAND_RPC_HARD_FORK_INFO::request> req_t = AUTO_VAL_INIT(req_t);
epee::json_rpc::response<cryptonote::COMMAND_RPC_HARD_FORK_INFO::response, std::string> resp_t = AUTO_VAL_INIT(resp_t);
m_daemon_rpc_mutex.lock();
- bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/getheight", req, res, m_http_client);
- m_daemon_rpc_mutex.unlock();
- CHECK_AND_ASSERT_MES(r, false, "Failed to connect to daemon");
- CHECK_AND_ASSERT_MES(res.status != CORE_RPC_STATUS_BUSY, false, "Failed to connect to daemon");
- CHECK_AND_ASSERT_MES(res.status == CORE_RPC_STATUS_OK, false, "Failed to get current blockchain height");
-
- m_daemon_rpc_mutex.lock();
req_t.jsonrpc = "2.0";
req_t.id = epee::serialization::storage_entry(0);
req_t.method = "hard_fork_info";
req_t.params.version = version;
- r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client);
+ 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();
+ CHECK_AND_ASSERT_THROW_MES(r, "Failed to connect to daemon");
+ CHECK_AND_ASSERT_THROW_MES(resp_t.result.status != CORE_RPC_STATUS_BUSY, "Failed to connect to daemon");
+ CHECK_AND_ASSERT_THROW_MES(resp_t.result.status == CORE_RPC_STATUS_OK, "Failed to get hard fork status");
+
+ earliest_height = resp_t.result.earliest_height;
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::use_fork_rules(uint8_t version, uint64_t early_blocks)
+{
+ cryptonote::COMMAND_RPC_GET_HEIGHT::request req = AUTO_VAL_INIT(req);
+ cryptonote::COMMAND_RPC_GET_HEIGHT::response res = AUTO_VAL_INIT(res);
+
+ m_daemon_rpc_mutex.lock();
+ bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/getheight", req, res, m_http_client);
m_daemon_rpc_mutex.unlock();
CHECK_AND_ASSERT_MES(r, false, "Failed to connect to daemon");
- CHECK_AND_ASSERT_MES(resp_t.result.status != CORE_RPC_STATUS_BUSY, false, "Failed to connect to daemon");
- CHECK_AND_ASSERT_MES(resp_t.result.status == CORE_RPC_STATUS_OK, false, "Failed to get hard fork status");
+ CHECK_AND_ASSERT_MES(res.status != CORE_RPC_STATUS_BUSY, false, "Failed to connect to daemon");
+ CHECK_AND_ASSERT_MES(res.status == CORE_RPC_STATUS_OK, false, "Failed to get current blockchain height");
+
+ uint64_t earliest_height;
+ get_hard_fork_info(version, earliest_height); // can throw
- bool close_enough = res.height >= resp_t.result.earliest_height - 10; // start using the rules a bit beforehand
+ bool close_enough = res.height >= earliest_height - early_blocks; // start using the rules that many blocks beforehand
if (close_enough)
- LOG_PRINT_L2("Using HF1 rules");
+ LOG_PRINT_L2("Using v" << (unsigned)version << " rules");
else
- LOG_PRINT_L2("Not using HF1 rules");
+ LOG_PRINT_L2("Not using v" << (unsigned)version << " rules");
return close_enough;
}
//----------------------------------------------------------------------------------------------------
@@ -3150,7 +3774,7 @@ uint64_t wallet2::get_upper_tranaction_size_limit()
{
if (m_upper_transaction_size_limit > 0)
return m_upper_transaction_size_limit;
- uint64_t full_reward_zone = use_fork_rules(2) ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 : CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1;
+ uint64_t full_reward_zone = use_fork_rules(2, 10) ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 : CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1;
return ((full_reward_zone * 125) / 100) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE;
}
//----------------------------------------------------------------------------------------------------
@@ -3213,6 +3837,8 @@ std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t co
}
return select_available_outputs([mixable, atleast](const transfer_details &td) {
+ if (td.is_rct())
+ return false;
const uint64_t amount = td.amount();
if (atleast) {
if (mixable.find(amount) != mixable.end())
@@ -3226,6 +3852,28 @@ std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t co
});
}
//----------------------------------------------------------------------------------------------------
+uint64_t wallet2::get_num_rct_outputs()
+{
+ epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request> req_t = AUTO_VAL_INIT(req_t);
+ epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response, std::string> resp_t = AUTO_VAL_INIT(resp_t);
+ m_daemon_rpc_mutex.lock();
+ req_t.jsonrpc = "2.0";
+ req_t.id = epee::serialization::storage_entry(0);
+ req_t.method = "get_output_histogram";
+ req_t.params.amounts.push_back(0);
+ req_t.params.min_count = 0;
+ req_t.params.max_count = 0;
+ 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, "get_num_rct_outputs");
+ THROW_WALLET_EXCEPTION_IF(resp_t.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram");
+ THROW_WALLET_EXCEPTION_IF(resp_t.result.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.result.status);
+ 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;
+}
+//----------------------------------------------------------------------------------------------------
std::vector<size_t> wallet2::select_available_unmixable_outputs(bool trusted_daemon)
{
// request all outputs with less than 3 instances
@@ -3241,7 +3889,7 @@ std::vector<size_t> wallet2::select_available_mixable_outputs(bool trusted_daemo
std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bool trusted_daemon)
{
// From hard fork 1, we don't consider small amounts to be dust anymore
- const bool hf1_rules = use_fork_rules(2); // first hard fork has version 2
+ const bool hf1_rules = use_fork_rules(2, 10); // first hard fork has version 2
tx_dust_policy dust_policy(hf1_rules ? 0 : ::config::DEFAULT_DUST_THRESHOLD);
// may throw
@@ -3290,7 +3938,9 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo
// mark transfers to be used as "spent"
BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers)
- it->m_spent = true;
+ {
+ set_spent(*it, 0);
+ }
}
// if we made it this far, we've selected our transactions. committing them will mark them spent,
@@ -3300,8 +3950,9 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo
{
// mark transfers to be used as not spent
BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers)
- it2->m_spent = false;
-
+ {
+ set_unspent(*it2);
+ }
}
// if we made it this far, we're OK to actually send the transactions
@@ -3317,8 +3968,9 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo
{
// mark transfers to be used as not spent
BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers)
- it2->m_spent = false;
-
+ {
+ set_unspent(*it2);
+ }
}
if (attempt_count >= MAX_SPLIT_ATTEMPTS)
@@ -3335,8 +3987,9 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo
{
// mark transfers to be used as not spent
BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers)
- it2->m_spent = false;
-
+ {
+ set_unspent(*it2);
+ }
}
throw;
@@ -3521,7 +4174,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
for (size_t n = 0; n < daemon_resp.spent_status.size(); ++n)
{
transfer_details &td = m_transfers[n];
- uint64_t amount = td.m_tx.vout[td.m_internal_output_index].amount;
+ uint64_t amount = td.amount();
td.m_spent = daemon_resp.spent_status[n] != COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT;
if (td.m_spent)
spent += amount;