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.cpp1667
1 files changed, 1404 insertions, 263 deletions
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index dc4f4879b..c6ba14dea 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -81,8 +81,8 @@ using namespace cryptonote;
// arbitrary, used to generate different hashes from the same input
#define CHACHA8_KEY_TAIL 0x8c
-#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\003"
-#define SIGNED_TX_PREFIX "Monero signed tx set\003"
+#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\004"
+#define SIGNED_TX_PREFIX "Monero signed tx set\004"
#define RECENT_OUTPUT_RATIO (0.5) // 50% of outputs are from the recent zone
#define RECENT_OUTPUT_ZONE ((time_t)(1.8 * 86400)) // last 1.8 day makes up the recent zone (taken from monerolink.pdf, Miller et al)
@@ -91,6 +91,9 @@ using namespace cryptonote;
#define SECOND_OUTPUT_RELATEDNESS_THRESHOLD 0.0f
+#define SUBADDRESS_LOOKAHEAD_MAJOR 50
+#define SUBADDRESS_LOOKAHEAD_MINOR 200
+
#define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\002"
namespace
@@ -317,10 +320,8 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
// public key if it was not given
if (field_address_found)
{
- cryptonote::account_public_address address;
- bool has_payment_id;
- crypto::hash8 new_payment_id;
- if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, testnet, field_address))
+ cryptonote::address_parse_info info;
+ if(!get_account_address_from_str(info, testnet, field_address))
{
tools::fail_msg_writer() << tools::wallet2::tr("invalid address");
return false;
@@ -332,7 +333,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
tools::fail_msg_writer() << tools::wallet2::tr("failed to verify view key secret key");
return false;
}
- if (address.m_view_public_key != pkey) {
+ if (info.address.m_view_public_key != pkey) {
tools::fail_msg_writer() << tools::wallet2::tr("view key does not match standard address");
return false;
}
@@ -344,7 +345,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
tools::fail_msg_writer() << tools::wallet2::tr("failed to verify spend key secret key");
return false;
}
- if (address.m_spend_public_key != pkey) {
+ if (info.address.m_spend_public_key != pkey) {
tools::fail_msg_writer() << tools::wallet2::tr("spend key does not match standard address");
return false;
}
@@ -381,15 +382,13 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
// from the address
if (field_address_found)
{
- cryptonote::account_public_address address2;
- bool has_payment_id;
- crypto::hash8 new_payment_id;
- if (!get_account_integrated_address_from_str(address2, has_payment_id, new_payment_id, testnet, field_address))
+ cryptonote::address_parse_info info;
+ if(!get_account_address_from_str(info, testnet, field_address))
{
tools::fail_msg_writer() << tools::wallet2::tr("failed to parse address: ") << field_address;
return false;
}
- address.m_spend_public_key = address2.m_spend_public_key;
+ address.m_spend_public_key = info.address.m_spend_public_key;
}
wallet->generate(field_filename, field_password, address, viewkey);
}
@@ -526,7 +525,7 @@ std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::varia
}
//----------------------------------------------------------------------------------------------------
-bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, uint64_t upper_transaction_size_limit)
+bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, uint64_t upper_transaction_size_limit, bool ssl)
{
m_checkpoints.init_default_checkpoints(m_testnet);
if(m_http_client.is_connected())
@@ -535,7 +534,10 @@ bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::
m_upper_transaction_size_limit = upper_transaction_size_limit;
m_daemon_address = std::move(daemon_address);
m_daemon_login = std::move(daemon_login);
- return m_http_client.set_server(get_daemon_address(), get_daemon_login());
+ // When switching from light wallet to full wallet, we need to reset the height we got from lw node.
+ if(m_light_wallet)
+ m_local_bc_height = m_blockchain.size();
+ return m_http_client.set_server(get_daemon_address(), get_daemon_login(), ssl);
}
//----------------------------------------------------------------------------------------------------
bool wallet2::is_deterministic() const
@@ -583,6 +585,129 @@ void wallet2::set_seed_language(const std::string &language)
{
seed_language = language;
}
+//----------------------------------------------------------------------------------------------------
+cryptonote::account_public_address wallet2::get_subaddress(const cryptonote::subaddress_index& index) const
+{
+ const cryptonote::account_keys& keys = m_account.get_keys();
+ if (index.is_zero())
+ return keys.m_account_address;
+
+ crypto::public_key D = get_subaddress_spend_public_key(index);
+
+ // C = a*D
+ crypto::public_key C = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(D), rct::sk2rct(keys.m_view_secret_key))); // could have defined secret_key_mult_public_key() under src/crypto
+
+ // result: (C, D)
+ cryptonote::account_public_address address;
+ address.m_view_public_key = C;
+ address.m_spend_public_key = D;
+ return address;
+}
+//----------------------------------------------------------------------------------------------------
+crypto::public_key wallet2::get_subaddress_spend_public_key(const cryptonote::subaddress_index& index) const
+{
+ const cryptonote::account_keys& keys = m_account.get_keys();
+ if (index.is_zero())
+ return keys.m_account_address.m_spend_public_key;
+
+ // m = Hs(a || index_major || index_minor)
+ crypto::secret_key m = cryptonote::get_subaddress_secret_key(keys.m_view_secret_key, index);
+
+ // M = m*G
+ crypto::public_key M;
+ crypto::secret_key_to_public_key(m, M);
+
+ // D = B + M
+ rct::key D_rct;
+ rct::addKeys(D_rct, rct::pk2rct(keys.m_account_address.m_spend_public_key), rct::pk2rct(M)); // could have defined add_public_key() under src/crypto
+ crypto::public_key D = rct::rct2pk(D_rct);
+
+ return D;
+}
+//----------------------------------------------------------------------------------------------------
+std::string wallet2::get_subaddress_as_str(const cryptonote::subaddress_index& index) const
+{
+ cryptonote::account_public_address address = get_subaddress(index);
+ return cryptonote::get_account_address_as_str(m_testnet, !index.is_zero(), address);
+}
+//----------------------------------------------------------------------------------------------------
+std::string wallet2::get_integrated_address_as_str(const crypto::hash8& payment_id) const
+{
+ return cryptonote::get_account_integrated_address_as_str(m_testnet, get_address(), payment_id);
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::add_subaddress_account(const std::string& label)
+{
+ uint32_t index_major = (uint32_t)get_num_subaddress_accounts();
+ expand_subaddresses({index_major, 0});
+ m_subaddress_labels[index_major][0] = label;
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::add_subaddress(uint32_t index_major, const std::string& label)
+{
+ if (index_major >= m_subaddress_labels.size())
+ throw std::runtime_error("index_major is out of bound");
+ uint32_t index_minor = (uint32_t)get_num_subaddresses(index_major);
+ expand_subaddresses({index_major, index_minor});
+ m_subaddress_labels[index_major][index_minor] = label;
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::expand_subaddresses(const cryptonote::subaddress_index& index)
+{
+ if (m_subaddress_labels.size() <= index.major)
+ {
+ // add new accounts
+ cryptonote::subaddress_index index2;
+ for (index2.major = m_subaddress_labels.size(); index2.major < index.major + SUBADDRESS_LOOKAHEAD_MAJOR; ++index2.major)
+ {
+ for (index2.minor = 0; index2.minor < (index2.major == index.major ? index.minor : 0) + SUBADDRESS_LOOKAHEAD_MINOR; ++index2.minor)
+ {
+ if (m_subaddresses_inv.count(index2) == 0)
+ {
+ crypto::public_key D = get_subaddress_spend_public_key(index2);
+ m_subaddresses[D] = index2;
+ m_subaddresses_inv[index2] = D;
+ }
+ }
+ }
+ m_subaddress_labels.resize(index.major + 1, {"Untitled account"});
+ m_subaddress_labels[index.major].resize(index.minor + 1);
+ }
+ else if (m_subaddress_labels[index.major].size() <= index.minor)
+ {
+ // add new subaddresses
+ cryptonote::subaddress_index index2 = index;
+ for (index2.minor = m_subaddress_labels[index.major].size(); index2.minor < index.minor + SUBADDRESS_LOOKAHEAD_MINOR; ++index2.minor)
+ {
+ if (m_subaddresses_inv.count(index2) == 0)
+ {
+ crypto::public_key D = get_subaddress_spend_public_key(index2);
+ m_subaddresses[D] = index2;
+ m_subaddresses_inv[index2] = D;
+ }
+ }
+ m_subaddress_labels[index.major].resize(index.minor + 1);
+ }
+}
+//----------------------------------------------------------------------------------------------------
+std::string wallet2::get_subaddress_label(const cryptonote::subaddress_index& index) const
+{
+ if (index.major >= m_subaddress_labels.size())
+ throw std::runtime_error("index.major is out of bound");
+ if (index.minor >= m_subaddress_labels[index.major].size())
+ throw std::runtime_error("index.minor is out of bound");
+ return m_subaddress_labels[index.major][index.minor];
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::set_subaddress_label(const cryptonote::subaddress_index& index, const std::string &label)
+{
+ if (index.major >= m_subaddress_labels.size())
+ throw std::runtime_error("index.major is out of bound");
+ if (index.minor >= m_subaddress_labels[index.major].size())
+ throw std::runtime_error("index.minor is out of bound");
+ m_subaddress_labels[index.major][index.minor] = label;
+}
+//----------------------------------------------------------------------------------------------------
/*!
* \brief Tells if the wallet file is deprecated.
*/
@@ -607,7 +732,7 @@ void wallet2::set_unspent(size_t idx)
td.m_spent_height = 0;
}
//----------------------------------------------------------------------------------------------------
-void wallet2::check_acc_out_precomp(const crypto::public_key &spend_public_key, const tx_out &o, const crypto::key_derivation &derivation, size_t i, tx_scan_info_t &tx_scan_info) const
+void wallet2::check_acc_out_precomp(const tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, tx_scan_info_t &tx_scan_info) const
{
if (o.target.type() != typeid(txout_to_key))
{
@@ -615,7 +740,7 @@ void wallet2::check_acc_out_precomp(const crypto::public_key &spend_public_key,
LOG_ERROR("wrong type id in transaction out");
return;
}
- tx_scan_info.received = is_out_to_acc_precomp(spend_public_key, boost::get<txout_to_key>(o.target), derivation, i);
+ tx_scan_info.received = is_out_to_acc_precomp(m_subaddresses, boost::get<txout_to_key>(o.target).key, derivation, additional_derivations, i);
if(tx_scan_info.received)
{
tx_scan_info.money_transfered = o.amount; // may be 0 for ringct outputs
@@ -627,15 +752,8 @@ void wallet2::check_acc_out_precomp(const crypto::public_key &spend_public_key,
tx_scan_info.error = false;
}
//----------------------------------------------------------------------------------------------------
-static uint64_t decodeRct(const rct::rctSig & rv, const crypto::public_key &pub, const crypto::secret_key &sec, unsigned int i, rct::key & mask)
+static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation &derivation, 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
@@ -658,9 +776,9 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::public_key &pub,
}
}
//----------------------------------------------------------------------------------------------------
-void wallet2::scan_output(const cryptonote::account_keys &keys, const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, uint64_t &tx_money_got_in_outs, std::vector<size_t> &outs)
+void wallet2::scan_output(const cryptonote::account_keys &keys, const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs)
{
- bool r = cryptonote::generate_key_image_helper(keys, tx_pub_key, i, tx_scan_info.in_ephemeral, tx_scan_info.ki);
+ bool r = cryptonote::generate_key_image_helper_precomp(keys, boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki);
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image");
THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.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");
@@ -668,9 +786,9 @@ void wallet2::scan_output(const cryptonote::account_keys &keys, const cryptonote
outs.push_back(i);
if (tx_scan_info.money_transfered == 0)
{
- tx_scan_info.money_transfered = tools::decodeRct(tx.rct_signatures, tx_pub_key, keys.m_view_secret_key, i, tx_scan_info.mask);
+ tx_scan_info.money_transfered = tools::decodeRct(tx.rct_signatures, tx_scan_info.received->derivation, i, tx_scan_info.mask);
}
- tx_money_got_in_outs += tx_scan_info.money_transfered;
+ tx_money_got_in_outs[tx_scan_info.received->index] = tx_scan_info.money_transfered;
tx_scan_info.amount = tx_scan_info.money_transfered;
++num_vouts_received;
}
@@ -680,10 +798,10 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
// In this function, tx (probably) only contains the base information
// (that is, the prunable stuff may or may not be included)
- if (!miner_tx)
+ if (!miner_tx && !pool)
process_unconfirmed(txid, tx, height);
std::vector<size_t> outs;
- uint64_t tx_money_got_in_outs = 0;
+ std::unordered_map<cryptonote::subaddress_index, uint64_t> tx_money_got_in_outs; // per receiving subaddress index
crypto::public_key tx_pub_key = null_pkey;
std::vector<tx_extra_field> tx_extra_fields;
@@ -695,6 +813,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
// Don't try to extract tx public key if tx has no ouputs
size_t pk_index = 0;
+ std::vector<tx_scan_info_t> tx_scan_info(tx.vout.size());
while (!tx.vout.empty())
{
// if tx.vout is not empty, we loop through all tx pubkeys
@@ -715,17 +834,26 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
bool r = true;
tools::threadpool& tpool = tools::threadpool::getInstance();
tools::threadpool::waiter waiter;
- std::unique_ptr<tx_scan_info_t[]> tx_scan_info{new tx_scan_info_t[tx.vout.size()]};
const cryptonote::account_keys& keys = m_account.get_keys();
crypto::key_derivation derivation;
generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation);
+
+ // additional tx pubkeys and derivations for multi-destination transfers involving one or more subaddresses
+ std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx);
+ std::vector<crypto::key_derivation> additional_derivations;
+ for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i)
+ {
+ additional_derivations.push_back({});
+ generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back());
+ }
+
if (miner_tx && m_refresh_type == RefreshNoCoinbase)
{
// assume coinbase isn't for us
}
else if (miner_tx && m_refresh_type == RefreshOptimizeCoinbase)
{
- check_acc_out_precomp(keys.m_account_address.m_spend_public_key, tx.vout[0], derivation, 0, tx_scan_info[0]);
+ check_acc_out_precomp(tx.vout[0], derivation, additional_derivations, 0, tx_scan_info[0]);
if (tx_scan_info[0].error)
{
r = false;
@@ -741,7 +869,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
// the first one was already checked
for (size_t i = 1; i < tx.vout.size(); ++i)
{
- tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(keys.m_account_address.m_spend_public_key), std::cref(tx.vout[i]), std::cref(derivation), i,
+ tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(tx.vout[i]), std::cref(derivation), std::cref(additional_derivations), i,
std::ref(tx_scan_info[i])));
}
waiter.wait();
@@ -768,11 +896,10 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
for (size_t i = 0; i < tx.vout.size(); ++i)
{
- tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(keys.m_account_address.m_spend_public_key),
- std::cref(tx.vout[i]), std::cref(derivation), i, std::ref(tx_scan_info[i])));
+ tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(tx.vout[i]), std::cref(derivation), std::cref(additional_derivations), i,
+ std::ref(tx_scan_info[i])));
}
waiter.wait();
-
for (size_t i = 0; i < tx.vout.size(); ++i)
{
if (tx_scan_info[i].error)
@@ -790,7 +917,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
{
for (size_t i = 0; i < tx.vout.size(); ++i)
{
- check_acc_out_precomp(keys.m_account_address.m_spend_public_key, tx.vout[i], derivation, i, tx_scan_info[i]);
+ check_acc_out_precomp(tx.vout[i], derivation, additional_derivations, i, tx_scan_info[i]);
if (tx_scan_info[i].error)
{
r = false;
@@ -840,6 +967,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
td.m_key_image_known = !m_watch_only;
td.m_amount = tx.vout[o].amount;
td.m_pk_index = pk_index - 1;
+ td.m_subaddr_index = tx_scan_info[o].received->index;
+ expand_subaddresses(tx_scan_info[o].received->index);
if (td.m_amount == 0)
{
td.m_mask = tx_scan_info[o].mask;
@@ -861,7 +990,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
m_pub_keys[tx_scan_info[o].in_ephemeral.pub] = m_transfers.size()-1;
LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid);
if (0 != m_callback)
- m_callback->on_money_received(height, txid, tx, td.m_amount);
+ m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index);
}
}
else if (m_transfers[kit->second].m_spent || m_transfers[kit->second].amount() >= tx.vout[o].amount)
@@ -877,7 +1006,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
<< " from received " << print_money(tx.vout[o].amount) << " output already exists with "
<< print_money(m_transfers[kit->second].amount()) << ", replacing with new output");
// The new larger output replaced a previous smaller one
- tx_money_got_in_outs -= tx.vout[o].amount;
+ tx_money_got_in_outs[tx_scan_info[o].received->index] -= tx.vout[o].amount;
if (!pool)
{
@@ -889,6 +1018,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
td.m_txid = txid;
td.m_amount = tx.vout[o].amount;
td.m_pk_index = pk_index - 1;
+ td.m_subaddr_index = tx_scan_info[o].received->index;
+ expand_subaddresses(tx_scan_info[o].received->index);
if (td.m_amount == 0)
{
td.m_mask = tx_scan_info[o].mask;
@@ -910,7 +1041,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid);
if (0 != m_callback)
- m_callback->on_money_received(height, txid, tx, td.m_amount);
+ m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index);
}
}
}
@@ -918,6 +1049,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
}
uint64_t tx_money_spent_in_ins = 0;
+ boost::optional<uint32_t> subaddr_account;
+ std::set<uint32_t> subaddr_indices;
// check all outputs for spending (compare key images)
for(auto& in: tx.vin)
{
@@ -936,23 +1069,50 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
}
amount = td.amount();
tx_money_spent_in_ins += amount;
+ if (subaddr_account && *subaddr_account != td.m_subaddr_index.major)
+ LOG_ERROR("spent funds are from different subaddress accounts; count of incoming/outgoing payments will be incorrect");
+ subaddr_account = td.m_subaddr_index.major;
+ subaddr_indices.insert(td.m_subaddr_index.minor);
if (!pool)
{
LOG_PRINT_L0("Spent money: " << print_money(amount) << ", with tx: " << txid);
set_spent(it->second, height);
if (0 != m_callback)
- m_callback->on_money_spent(height, txid, tx, amount, tx);
+ m_callback->on_money_spent(height, txid, tx, amount, tx, td.m_subaddr_index);
}
}
}
- if (tx_money_spent_in_ins > 0)
+ if (tx_money_spent_in_ins > 0 && !pool)
+ {
+ uint64_t self_received = std::accumulate<decltype(tx_money_got_in_outs.begin()), uint64_t>(tx_money_got_in_outs.begin(), tx_money_got_in_outs.end(), 0,
+ [&subaddr_account] (uint64_t acc, const std::pair<cryptonote::subaddress_index, uint64_t>& p)
+ {
+ return acc + (p.first.major == *subaddr_account ? p.second : 0);
+ });
+ process_outgoing(txid, tx, height, ts, tx_money_spent_in_ins, self_received, *subaddr_account, subaddr_indices);
+ // if sending to yourself at the same subaddress account, set the outgoing payment amount to 0 so that it's less confusing
+ uint64_t fee = tx.version == 1 ? tx_money_spent_in_ins - get_outs_money_amount(tx) : tx.rct_signatures.txnFee;
+ if (tx_money_spent_in_ins == self_received + fee)
+ {
+ auto i = m_confirmed_txs.find(txid);
+ THROW_WALLET_EXCEPTION_IF(i == m_confirmed_txs.end(), error::wallet_internal_error,
+ "confirmed tx wasn't found: " + string_tools::pod_to_hex(txid));
+ i->second.m_change = self_received;
+ }
+ }
+
+ // remove change sent to the spending subaddress account from the list of received funds
+ for (auto i = tx_money_got_in_outs.begin(); i != tx_money_got_in_outs.end();)
{
- process_outgoing(txid, tx, height, ts, tx_money_spent_in_ins, tx_money_got_in_outs);
+ if (subaddr_account && i->first.major == *subaddr_account)
+ i = tx_money_got_in_outs.erase(i);
+ else
+ ++i;
}
- uint64_t received = (tx_money_spent_in_ins < tx_money_got_in_outs) ? tx_money_got_in_outs - tx_money_spent_in_ins : 0;
- if (0 < received)
+ // create payment_details for each incoming transfer to a subaddress index
+ if (tx_money_got_in_outs.size() > 0)
{
tx_extra_nonce extra_nonce;
crypto::hash payment_id = null_hash;
@@ -993,20 +1153,24 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
LOG_PRINT_L2("Found unencrypted payment ID: " << payment_id);
}
- payment_details payment;
- payment.m_tx_hash = txid;
- payment.m_amount = received;
- payment.m_block_height = height;
- payment.m_unlock_time = tx.unlock_time;
- payment.m_timestamp = ts;
- if (pool) {
- m_unconfirmed_payments.emplace(payment_id, payment);
- if (0 != m_callback)
- m_callback->on_unconfirmed_money_received(height, txid, tx, payment.m_amount);
+ for (const auto& i : tx_money_got_in_outs)
+ {
+ payment_details payment;
+ payment.m_tx_hash = txid;
+ payment.m_amount = i.second;
+ payment.m_block_height = height;
+ payment.m_unlock_time = tx.unlock_time;
+ payment.m_timestamp = ts;
+ payment.m_subaddr_index = i.first;
+ if (pool) {
+ m_unconfirmed_payments.emplace(payment_id, payment);
+ if (0 != m_callback)
+ m_callback->on_unconfirmed_money_received(height, txid, tx, payment.m_amount, payment.m_subaddr_index);
+ }
+ else
+ m_payments.emplace(payment_id, payment);
+ LOG_PRINT_L2("Payment found in " << (pool ? "pool" : "block") << ": " << payment_id << " / " << payment.m_tx_hash << " / " << payment.m_amount);
}
- else
- m_payments.emplace(payment_id, payment);
- LOG_PRINT_L2("Payment found in " << (pool ? "pool" : "block") << ": " << payment_id << " / " << payment.m_tx_hash << " / " << payment.m_amount);
}
}
//----------------------------------------------------------------------------------------------------
@@ -1030,7 +1194,7 @@ void wallet2::process_unconfirmed(const crypto::hash &txid, const cryptonote::tr
}
}
//----------------------------------------------------------------------------------------------------
-void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::transaction &tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received)
+void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::transaction &tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received, uint32_t subaddr_account, const std::set<uint32_t>& subaddr_indices)
{
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
@@ -1056,6 +1220,8 @@ void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::trans
get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, entry.first->second.m_payment_id);
}
}
+ entry.first->second.m_subaddr_account = subaddr_account;
+ entry.first->second.m_subaddr_indices = subaddr_indices;
}
entry.first->second.m_block_height = height;
entry.first->second.m_timestamp = ts;
@@ -1350,6 +1516,34 @@ void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_hei
error = true;
}
}
+
+void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes)
+{
+ // remove pool txes to us that aren't in the pool anymore
+ std::unordered_map<crypto::hash, wallet2::payment_details>::iterator uit = m_unconfirmed_payments.begin();
+ while (uit != m_unconfirmed_payments.end())
+ {
+ const crypto::hash &txid = uit->second.m_tx_hash;
+ bool found = false;
+ for (const auto &it2: tx_hashes)
+ {
+ if (it2 == txid)
+ {
+ found = true;
+ break;
+ }
+ }
+ auto pit = uit++;
+ if (!found)
+ {
+ MDEBUG("Removing " << txid << " from unconfirmed payments, not found in pool");
+ m_unconfirmed_payments.erase(pit);
+ if (0 != m_callback)
+ m_callback->on_pool_tx_removed(txid);
+ }
+ }
+}
+
//----------------------------------------------------------------------------------------------------
void wallet2::update_pool_state(bool refreshed)
{
@@ -1427,28 +1621,8 @@ void wallet2::update_pool_state(bool refreshed)
// the in transfers list instead (or nowhere if it just
// disappeared without being mined)
if (refreshed)
- {
- std::unordered_map<crypto::hash, wallet2::payment_details>::iterator uit = m_unconfirmed_payments.begin();
- while (uit != m_unconfirmed_payments.end())
- {
- const crypto::hash &txid = uit->second.m_tx_hash;
- bool found = false;
- for (const auto &it2: res.tx_hashes)
- {
- if (it2 == txid)
- {
- found = true;
- break;
- }
- }
- auto pit = uit++;
- if (!found)
- {
- MDEBUG("Removing " << txid << " from unconfirmed payments, not found in pool");
- m_unconfirmed_payments.erase(pit);
- }
- }
- }
+ remove_obsolete_pool_txs(res.tx_hashes);
+
MDEBUG("update_pool_state done second loop");
// gather txids of new pool txes to us
@@ -1478,6 +1652,18 @@ void wallet2::update_pool_state(bool refreshed)
if (i.first == txid)
{
found = true;
+ // if this is a payment to yourself at a different subaddress account, don't skip it
+ // so that you can see the incoming pool tx with 'show_transfers' on that receiving subaddress account
+ const unconfirmed_transfer_details& utd = i.second;
+ for (const auto& dst : utd.m_dests)
+ {
+ auto subaddr_index = m_subaddresses.find(dst.addr.m_spend_public_key);
+ if (subaddr_index != m_subaddresses.end() && subaddr_index->second.major != utd.m_subaddr_account)
+ {
+ found = false;
+ break;
+ }
+ }
break;
}
}
@@ -1626,12 +1812,13 @@ void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height,
}
-bool wallet2::add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description)
+bool wallet2::add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description, bool is_subaddress)
{
wallet2::address_book_row a;
a.m_address = address;
a.m_payment_id = payment_id;
a.m_description = description;
+ a.m_is_subaddress = is_subaddress;
auto old_size = m_address_book.size();
m_address_book.push_back(a);
@@ -1652,6 +1839,39 @@ bool wallet2::delete_address_book_row(std::size_t row_id) {
//----------------------------------------------------------------------------------------------------
void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& received_money)
{
+ if(m_light_wallet) {
+
+ // MyMonero get_address_info needs to be called occasionally to trigger wallet sync.
+ // This call is not really needed for other purposes and can be removed if mymonero changes their backend.
+ cryptonote::COMMAND_RPC_GET_ADDRESS_INFO::response res;
+
+ // Get basic info
+ if(light_wallet_get_address_info(res)) {
+ // Last stored block height
+ uint64_t prev_height = m_light_wallet_blockchain_height;
+ // Update lw heights
+ m_light_wallet_scanned_block_height = res.scanned_block_height;
+ m_light_wallet_blockchain_height = res.blockchain_height;
+ m_local_bc_height = res.blockchain_height;
+ // If new height - call new_block callback
+ if(m_light_wallet_blockchain_height != prev_height)
+ {
+ MDEBUG("new block since last time!");
+ m_callback->on_lw_new_block(m_light_wallet_blockchain_height - 1);
+ }
+ m_light_wallet_connected = true;
+ MDEBUG("lw scanned block height: " << m_light_wallet_scanned_block_height);
+ MDEBUG("lw blockchain height: " << m_light_wallet_blockchain_height);
+ MDEBUG(m_light_wallet_blockchain_height-m_light_wallet_scanned_block_height << " blocks behind");
+ // TODO: add wallet created block info
+
+ light_wallet_get_address_txs();
+ } else
+ m_light_wallet_connected = false;
+
+ // Lighwallet refresh done
+ return;
+ }
received_money = false;
blocks_fetched = 0;
uint64_t added_blocks = 0;
@@ -1750,7 +1970,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re
LOG_PRINT_L1("Failed to check pending transactions");
}
- LOG_PRINT_L1("Refresh done, blocks received: " << blocks_fetched << ", balance: " << print_money(balance()) << ", unlocked: " << print_money(unlocked_balance()));
+ LOG_PRINT_L1("Refresh done, blocks received: " << blocks_fetched << ", balance (all accounts): " << print_money(balance_all()) << ", unlocked: " << print_money(unlocked_balance_all()));
}
//----------------------------------------------------------------------------------------------------
bool wallet2::refresh(uint64_t & blocks_fetched, bool& received_money, bool& ok)
@@ -1845,12 +2065,16 @@ bool wallet2::clear()
m_unconfirmed_txs.clear();
m_payments.clear();
m_tx_keys.clear();
+ m_additional_tx_keys.clear();
m_confirmed_txs.clear();
m_unconfirmed_payments.clear();
m_scanned_pool_txs[0].clear();
m_scanned_pool_txs[1].clear();
m_address_book.clear();
m_local_bc_height = 1;
+ m_subaddresses.clear();
+ m_subaddresses_inv.clear();
+ m_subaddress_labels.clear();
return true;
}
@@ -2240,6 +2464,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const std::stri
cryptonote::block b;
generate_genesis(b);
m_blockchain.push_back(get_block_hash(b));
+ add_subaddress_account(tr("Primary account"));
store();
return retval;
@@ -2275,6 +2500,7 @@ void wallet2::generate(const std::string& wallet_, const std::string& password,
cryptonote::block b;
generate_genesis(b);
m_blockchain.push_back(get_block_hash(b));
+ add_subaddress_account(tr("Primary account"));
store();
}
@@ -2310,6 +2536,7 @@ void wallet2::generate(const std::string& wallet_, const std::string& password,
cryptonote::block b;
generate_genesis(b);
m_blockchain.push_back(get_block_hash(b));
+ add_subaddress_account(tr("Primary account"));
store();
}
@@ -2420,6 +2647,12 @@ bool wallet2::check_connection(uint32_t *version, uint32_t timeout)
boost::lock_guard<boost::mutex> lock(m_daemon_rpc_mutex);
+ // TODO: Add light wallet version check.
+ if(m_light_wallet) {
+ version = 0;
+ return m_light_wallet_connected;
+ }
+
if(!m_http_client.is_connected())
{
m_node_rpc_proxy.invalidate();
@@ -2560,6 +2793,9 @@ void wallet2::load(const std::string& wallet_, const std::string& password)
trim_hashchain();
+ if (get_num_subaddress_accounts() == 0)
+ add_subaddress_account(tr("Primary account"));
+
m_local_bc_height = m_blockchain.size();
}
//----------------------------------------------------------------------------------------------------
@@ -2707,52 +2943,113 @@ void wallet2::store_to(const std::string &path, const std::string &password)
}
}
//----------------------------------------------------------------------------------------------------
-uint64_t wallet2::unlocked_balance() const
+uint64_t wallet2::balance(uint32_t index_major) const
{
uint64_t amount = 0;
- for(const transfer_details& td: m_transfers)
- if(!td.m_spent && is_transfer_unlocked(td))
- amount += td.amount();
-
+ if(m_light_wallet)
+ return m_light_wallet_unlocked_balance;
+ for (const auto& i : balance_per_subaddress(index_major))
+ amount += i.second;
return amount;
}
//----------------------------------------------------------------------------------------------------
-uint64_t wallet2::balance() const
+uint64_t wallet2::unlocked_balance(uint32_t index_major) const
{
uint64_t amount = 0;
- for(auto& td: m_transfers)
- if(!td.m_spent)
- amount += td.amount();
-
-
- for(auto& utx: m_unconfirmed_txs)
- if (utx.second.m_state != wallet2::unconfirmed_transfer_details::failed)
- amount+= utx.second.m_change;
-
+ if(m_light_wallet)
+ return m_light_wallet_balance;
+ for (const auto& i : unlocked_balance_per_subaddress(index_major))
+ amount += i.second;
return amount;
}
//----------------------------------------------------------------------------------------------------
+std::map<uint32_t, uint64_t> wallet2::balance_per_subaddress(uint32_t index_major) const
+{
+ std::map<uint32_t, uint64_t> amount_per_subaddr;
+ for (const auto& td: m_transfers)
+ {
+ if (td.m_subaddr_index.major == index_major && !td.m_spent)
+ {
+ auto found = amount_per_subaddr.find(td.m_subaddr_index.minor);
+ if (found == amount_per_subaddr.end())
+ amount_per_subaddr[td.m_subaddr_index.minor] = td.amount();
+ else
+ found->second += td.amount();
+ }
+ }
+ for (const auto& utx: m_unconfirmed_txs)
+ {
+ if (utx.second.m_subaddr_account == index_major && utx.second.m_state != wallet2::unconfirmed_transfer_details::failed)
+ {
+ // all changes go to 0-th subaddress (in the current subaddress account)
+ auto found = amount_per_subaddr.find(0);
+ if (found == amount_per_subaddr.end())
+ amount_per_subaddr[0] = utx.second.m_change;
+ else
+ found->second += utx.second.m_change;
+ }
+ }
+ return amount_per_subaddr;
+}
+//----------------------------------------------------------------------------------------------------
+std::map<uint32_t, uint64_t> wallet2::unlocked_balance_per_subaddress(uint32_t index_major) const
+{
+ std::map<uint32_t, uint64_t> amount_per_subaddr;
+ for(const transfer_details& td: m_transfers)
+ {
+ if(td.m_subaddr_index.major == index_major && !td.m_spent && is_transfer_unlocked(td))
+ {
+ auto found = amount_per_subaddr.find(td.m_subaddr_index.minor);
+ if (found == amount_per_subaddr.end())
+ amount_per_subaddr[td.m_subaddr_index.minor] = td.amount();
+ else
+ found->second += td.amount();
+ }
+ }
+ return amount_per_subaddr;
+}
+//----------------------------------------------------------------------------------------------------
+uint64_t wallet2::balance_all() const
+{
+ uint64_t r = 0;
+ for (uint32_t index_major = 0; index_major < get_num_subaddress_accounts(); ++index_major)
+ r += balance(index_major);
+ return r;
+}
+//----------------------------------------------------------------------------------------------------
+uint64_t wallet2::unlocked_balance_all() const
+{
+ uint64_t r = 0;
+ for (uint32_t index_major = 0; index_major < get_num_subaddress_accounts(); ++index_major)
+ r += unlocked_balance(index_major);
+ return r;
+}
+//----------------------------------------------------------------------------------------------------
void wallet2::get_transfers(wallet2::transfer_container& incoming_transfers) const
{
incoming_transfers = m_transfers;
}
//----------------------------------------------------------------------------------------------------
-void wallet2::get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height) const
+void wallet2::get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const
{
auto range = m_payments.equal_range(payment_id);
- std::for_each(range.first, range.second, [&payments, &min_height](const payment_container::value_type& x) {
- if (min_height < x.second.m_block_height)
+ std::for_each(range.first, range.second, [&payments, &min_height, &subaddr_account, &subaddr_indices](const payment_container::value_type& x) {
+ if (min_height < x.second.m_block_height &&
+ (!subaddr_account || *subaddr_account == x.second.m_subaddr_index.major) &&
+ (subaddr_indices.empty() || subaddr_indices.count(x.second.m_subaddr_index.minor) == 1))
{
payments.push_back(x.second);
}
});
}
//----------------------------------------------------------------------------------------------------
-void wallet2::get_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& payments, uint64_t min_height, uint64_t max_height) const
+void wallet2::get_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& payments, uint64_t min_height, uint64_t max_height, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const
{
auto range = std::make_pair(m_payments.begin(), m_payments.end());
- std::for_each(range.first, range.second, [&payments, &min_height, &max_height](const payment_container::value_type& x) {
- if (min_height < x.second.m_block_height && max_height >= x.second.m_block_height)
+ std::for_each(range.first, range.second, [&payments, &min_height, &max_height, &subaddr_account, &subaddr_indices](const payment_container::value_type& x) {
+ if (min_height < x.second.m_block_height && max_height >= x.second.m_block_height &&
+ (!subaddr_account || *subaddr_account == x.second.m_subaddr_index.major) &&
+ (subaddr_indices.empty() || subaddr_indices.count(x.second.m_subaddr_index.minor) == 1))
{
payments.push_back(x);
}
@@ -2760,25 +3057,35 @@ void wallet2::get_payments(std::list<std::pair<crypto::hash,wallet2::payment_det
}
//----------------------------------------------------------------------------------------------------
void wallet2::get_payments_out(std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>>& confirmed_payments,
- uint64_t min_height, uint64_t max_height) const
+ uint64_t min_height, uint64_t max_height, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const
{
for (auto i = m_confirmed_txs.begin(); i != m_confirmed_txs.end(); ++i) {
- if (i->second.m_block_height > min_height && i->second.m_block_height <= max_height) {
- confirmed_payments.push_back(*i);
- }
+ if (i->second.m_block_height <= min_height || i->second.m_block_height > max_height)
+ continue;
+ if (subaddr_account && *subaddr_account != i->second.m_subaddr_account)
+ continue;
+ if (!subaddr_indices.empty() && std::count_if(i->second.m_subaddr_indices.begin(), i->second.m_subaddr_indices.end(), [&subaddr_indices](uint32_t index) { return subaddr_indices.count(index) == 1; }) == 0)
+ continue;
+ confirmed_payments.push_back(*i);
}
}
//----------------------------------------------------------------------------------------------------
-void wallet2::get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wallet2::unconfirmed_transfer_details>>& unconfirmed_payments) const
+void wallet2::get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wallet2::unconfirmed_transfer_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const
{
for (auto i = m_unconfirmed_txs.begin(); i != m_unconfirmed_txs.end(); ++i) {
+ if (subaddr_account && *subaddr_account != i->second.m_subaddr_account)
+ continue;
+ if (!subaddr_indices.empty() && std::count_if(i->second.m_subaddr_indices.begin(), i->second.m_subaddr_indices.end(), [&subaddr_indices](uint32_t index) { return subaddr_indices.count(index) == 1; }) == 0)
+ continue;
unconfirmed_payments.push_back(*i);
}
}
//----------------------------------------------------------------------------------------------------
-void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments) const
+void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const
{
for (auto i = m_unconfirmed_payments.begin(); i != m_unconfirmed_payments.end(); ++i) {
+ if ((!subaddr_account || *subaddr_account == i->second.m_subaddr_index.major) &&
+ (subaddr_indices.empty() || subaddr_indices.count(i->second.m_subaddr_index.minor) == 1))
unconfirmed_payments.push_back(*i);
}
}
@@ -2843,6 +3150,7 @@ void wallet2::rescan_blockchain(bool refresh)
generate_genesis(genesis);
crypto::hash genesis_hash = get_block_hash(genesis);
m_blockchain.push_back(genesis_hash);
+ add_subaddress_account(tr("Primary account"));
m_local_bc_height = 1;
if (refresh)
@@ -2851,10 +3159,15 @@ void wallet2::rescan_blockchain(bool refresh)
//----------------------------------------------------------------------------------------------------
bool wallet2::is_transfer_unlocked(const transfer_details& td) const
{
- if(!is_tx_spendtime_unlocked(td.m_tx.unlock_time, td.m_block_height))
+ return is_transfer_unlocked(td.m_tx.unlock_time, td.m_block_height);
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height) const
+{
+ if(!is_tx_spendtime_unlocked(unlock_time, block_height))
return false;
- if(td.m_block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE > m_blockchain.size())
+ if(block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE > m_local_bc_height)
return false;
return true;
@@ -2865,7 +3178,7 @@ bool wallet2::is_tx_spendtime_unlocked(uint64_t unlock_time, uint64_t block_heig
if(unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER)
{
//interpret as block index
- if(m_blockchain.size()-1 + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS >= unlock_time)
+ if(m_local_bc_height-1 + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS >= unlock_time)
return true;
else
return false;
@@ -3040,7 +3353,7 @@ 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, uint64_t amount_in, 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, uint32_t subaddr_account, const std::set<uint32_t>& subaddr_indices)
{
unconfirmed_transfer_details& utd = m_unconfirmed_txs[cryptonote::get_transaction_hash(tx)];
utd.m_amount_in = amount_in;
@@ -3055,6 +3368,8 @@ void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amo
utd.m_payment_id = payment_id;
utd.m_state = wallet2::unconfirmed_transfer_details::pending;
utd.m_timestamp = time(NULL);
+ utd.m_subaddr_account = subaddr_account;
+ utd.m_subaddr_indices = subaddr_indices;
}
//----------------------------------------------------------------------------------------------------
@@ -3168,25 +3483,42 @@ crypto::hash8 wallet2::get_short_payment_id(const pending_tx &ptx) const
void wallet2::commit_tx(pending_tx& ptx)
{
using namespace cryptonote;
- crypto::hash txid;
-
- COMMAND_RPC_SEND_RAW_TX::request req;
- req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx));
- req.do_not_relay = false;
- COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp;
- m_daemon_rpc_mutex.lock();
- bool r = epee::net_utils::invoke_http_json("/sendrawtransaction", req, daemon_send_resp, m_http_client, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "sendrawtransaction");
- THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "sendrawtransaction");
- THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status != CORE_RPC_STATUS_OK, error::tx_rejected, ptx.tx, daemon_send_resp.status, daemon_send_resp.reason);
-
- // sanity checks
- for (size_t idx: ptx.selected_transfers)
+
+ if(m_light_wallet)
{
- THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error,
- "Bad output index in selected transfers: " + boost::lexical_cast<std::string>(idx));
+ cryptonote::COMMAND_RPC_SUBMIT_RAW_TX::request oreq;
+ cryptonote::COMMAND_RPC_SUBMIT_RAW_TX::response ores;
+ oreq.address = get_account().get_public_address_str(m_testnet);
+ oreq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
+ oreq.tx = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx));
+ m_daemon_rpc_mutex.lock();
+ bool r = epee::net_utils::invoke_http_json("/submit_raw_tx", oreq, ores, m_http_client, rpc_timeout, "POST");
+ m_daemon_rpc_mutex.unlock();
+ THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "submit_raw_tx");
+ // MyMonero and OpenMonero use different status strings
+ THROW_WALLET_EXCEPTION_IF(ores.status != "OK" && ores.status != "success" , error::tx_rejected, ptx.tx, ores.status, ores.error);
}
+ else
+ {
+ // Normal submit
+ COMMAND_RPC_SEND_RAW_TX::request req;
+ req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx));
+ req.do_not_relay = false;
+ COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp;
+ m_daemon_rpc_mutex.lock();
+ bool r = epee::net_utils::invoke_http_json("/sendrawtransaction", req, daemon_send_resp, m_http_client, rpc_timeout);
+ m_daemon_rpc_mutex.unlock();
+ THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "sendrawtransaction");
+ THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "sendrawtransaction");
+ THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status != CORE_RPC_STATUS_OK, error::tx_rejected, ptx.tx, daemon_send_resp.status, daemon_send_resp.reason);
+ // sanity checks
+ for (size_t idx: ptx.selected_transfers)
+ {
+ THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error,
+ "Bad output index in selected transfers: " + boost::lexical_cast<std::string>(idx));
+ }
+ }
+ crypto::hash txid;
txid = get_transaction_hash(ptx.tx);
crypto::hash payment_id = crypto::null_hash;
@@ -3199,10 +3531,11 @@ void wallet2::commit_tx(pending_tx& ptx)
for(size_t idx: ptx.selected_transfers)
amount_in += m_transfers[idx].amount();
}
- add_unconfirmed_tx(ptx.tx, amount_in, dests, payment_id, ptx.change_dts.amount);
+ add_unconfirmed_tx(ptx.tx, amount_in, dests, payment_id, ptx.change_dts.amount, ptx.construction_data.subaddr_account, ptx.construction_data.subaddr_indices);
if (store_tx_info())
{
m_tx_keys.insert(std::make_pair(txid, ptx.tx_key));
+ m_additional_tx_keys.insert(std::make_pair(txid, ptx.additional_tx_keys));
}
LOG_PRINT_L2("transaction " << txid << " generated ok and sent to daemon, key_images: [" << ptx.key_images << "]");
@@ -3215,8 +3548,8 @@ void wallet2::commit_tx(pending_tx& ptx)
//fee includes dust if dust policy specified it.
LOG_PRINT_L1("Transaction successfully sent. <" << txid << ">" << ENDL
<< "Commission: " << print_money(ptx.fee) << " (dust sent to dust addr: " << print_money((ptx.dust_added_to_fee ? 0 : ptx.dust)) << ")" << ENDL
- << "Balance: " << print_money(balance()) << ENDL
- << "Unlocked: " << print_money(unlocked_balance()) << ENDL
+ << "Balance: " << print_money(balance(ptx.construction_data.subaddr_account)) << ENDL
+ << "Unlocked: " << print_money(unlocked_balance(ptx.construction_data.subaddr_account)) << ENDL
<< "Please, wait for confirmation for your balance to be unlocked.");
}
@@ -3270,7 +3603,8 @@ bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::stri
return false;
}
LOG_PRINT_L2("Saving unsigned tx data: " << oss.str());
- return epee::file_io_utils::save_string_to_file(filename, std::string(UNSIGNED_TX_PREFIX) + oss.str());
+ std::string ciphertext = encrypt_with_view_secret_key(oss.str());
+ return epee::file_io_utils::save_string_to_file(filename, std::string(UNSIGNED_TX_PREFIX) + ciphertext);
}
//----------------------------------------------------------------------------------------------------
bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx_set &exported_txs)
@@ -3288,22 +3622,55 @@ bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx
LOG_PRINT_L0("Failed to load from " << unsigned_filename);
return false;
}
- const size_t magiclen = strlen(UNSIGNED_TX_PREFIX);
+ const size_t magiclen = strlen(UNSIGNED_TX_PREFIX) - 1;
if (strncmp(s.c_str(), UNSIGNED_TX_PREFIX, magiclen))
{
LOG_PRINT_L0("Bad magic from " << unsigned_filename);
return false;
}
s = s.substr(magiclen);
- try
+ const char version = s[0];
+ s = s.substr(1);
+ if (version == '\003')
{
- std::istringstream iss(s);
- boost::archive::portable_binary_iarchive ar(iss);
- ar >> exported_txs;
+ try
+ {
+ std::istringstream iss(s);
+ boost::archive::portable_binary_iarchive ar(iss);
+ ar >> exported_txs;
+ }
+ catch (...)
+ {
+ LOG_PRINT_L0("Failed to parse data from " << unsigned_filename);
+ return false;
+ }
}
- catch (...)
+ else if (version == '\004')
{
- LOG_PRINT_L0("Failed to parse data from " << unsigned_filename);
+ try
+ {
+ s = decrypt_with_view_secret_key(s);
+ try
+ {
+ std::istringstream iss(s);
+ boost::archive::portable_binary_iarchive ar(iss);
+ ar >> exported_txs;
+ }
+ catch (...)
+ {
+ LOG_PRINT_L0("Failed to parse data from " << unsigned_filename);
+ return false;
+ }
+ }
+ catch (const std::exception &e)
+ {
+ LOG_PRINT_L0("Failed to decrypt " << unsigned_filename << ": " << e.what());
+ return false;
+ }
+ }
+ else
+ {
+ LOG_PRINT_L0("Unsupported version in " << unsigned_filename);
return false;
}
LOG_PRINT_L1("Loaded tx unsigned data from binary: " << exported_txs.txes.size() << " transactions");
@@ -3311,7 +3678,7 @@ bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx
return true;
}
//----------------------------------------------------------------------------------------------------
-bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs, std::function<bool(const unsigned_tx_set&)> accept_func)
+bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs, std::function<bool(const unsigned_tx_set&)> accept_func, bool export_raw)
{
unsigned_tx_set exported_txs;
if(!load_unsigned_tx(unsigned_filename, exported_txs))
@@ -3322,11 +3689,11 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s
LOG_PRINT_L1("Transactions rejected by callback");
return false;
}
- return sign_tx(exported_txs, signed_filename, txs);
+ return sign_tx(exported_txs, signed_filename, txs, export_raw);
}
//----------------------------------------------------------------------------------------------------
-bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs)
+bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs, bool export_raw)
{
import_outputs(exported_txs.transfers);
@@ -3339,7 +3706,8 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f
signed_txes.ptx.push_back(pending_tx());
tools::wallet2::pending_tx &ptx = signed_txes.ptx.back();
crypto::secret_key tx_key;
- bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sd.sources, sd.splitted_dsts, sd.extra, ptx.tx, sd.unlock_time, tx_key, sd.use_rct);
+ std::vector<crypto::secret_key> additional_tx_keys;
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_testnet);
// we don't test tx size, because we don't know the current limit, due to not having a blockchain,
// and it's a bit pointless to fail there anyway, since it'd be a (good) guess only. We sign anyway,
@@ -3353,6 +3721,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f
{
const crypto::hash txid = get_transaction_hash(ptx.tx);
m_tx_keys.insert(std::make_pair(txid, tx_key));
+ m_additional_tx_keys.insert(std::make_pair(txid, additional_tx_keys));
}
std::string key_images;
@@ -3399,8 +3768,28 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f
{
return false;
}
- LOG_PRINT_L3("Saving signed tx data: " << oss.str());
- return epee::file_io_utils::save_string_to_file(signed_filename, std::string(SIGNED_TX_PREFIX) + oss.str());
+ LOG_PRINT_L3("Saving signed tx data (with encryption): " << oss.str());
+ std::string ciphertext = encrypt_with_view_secret_key(oss.str());
+ if (!epee::file_io_utils::save_string_to_file(signed_filename, std::string(SIGNED_TX_PREFIX) + ciphertext))
+ {
+ LOG_PRINT_L0("Failed to save file to " << signed_filename);
+ return false;
+ }
+ // export signed raw tx without encryption
+ if (export_raw)
+ {
+ for (size_t i = 0; i < signed_txes.ptx.size(); ++i)
+ {
+ std::string tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(signed_txes.ptx[i].tx));
+ std::string raw_filename = signed_filename + "_raw" + (signed_txes.ptx.size() == 1 ? "" : ("_" + std::to_string(i)));
+ if (!epee::file_io_utils::save_string_to_file(raw_filename, tx_as_hex))
+ {
+ LOG_PRINT_L0("Failed to save file to " << raw_filename);
+ return false;
+ }
+ }
+ }
+ return true;
}
//----------------------------------------------------------------------------------------------------
bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func)
@@ -3420,22 +3809,55 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal
LOG_PRINT_L0("Failed to load from " << signed_filename);
return false;
}
- const size_t magiclen = strlen(SIGNED_TX_PREFIX);
+ const size_t magiclen = strlen(SIGNED_TX_PREFIX) - 1;
if (strncmp(s.c_str(), SIGNED_TX_PREFIX, magiclen))
{
LOG_PRINT_L0("Bad magic from " << signed_filename);
return false;
}
s = s.substr(magiclen);
- try
+ const char version = s[0];
+ s = s.substr(1);
+ if (version == '\003')
{
- std::istringstream iss(s);
- boost::archive::portable_binary_iarchive ar(iss);
- ar >> signed_txs;
+ try
+ {
+ std::istringstream iss(s);
+ boost::archive::portable_binary_iarchive ar(iss);
+ ar >> signed_txs;
+ }
+ catch (...)
+ {
+ LOG_PRINT_L0("Failed to parse data from " << signed_filename);
+ return false;
+ }
}
- catch (...)
+ else if (version == '\004')
{
- LOG_PRINT_L0("Failed to parse data from " << signed_filename);
+ try
+ {
+ s = decrypt_with_view_secret_key(s);
+ try
+ {
+ std::istringstream iss(s);
+ boost::archive::portable_binary_iarchive ar(iss);
+ ar >> signed_txs;
+ }
+ catch (...)
+ {
+ LOG_PRINT_L0("Failed to parse decrypted data from " << signed_filename);
+ return false;
+ }
+ }
+ catch (const std::exception &e)
+ {
+ LOG_PRINT_L0("Failed to decrypt " << signed_filename << ": " << e.what());
+ return false;
+ }
+ }
+ else
+ {
+ LOG_PRINT_L0("Unsupported version in " << signed_filename);
return false;
}
LOG_PRINT_L0("Loaded signed tx data from binary: " << signed_txs.ptx.size() << " transactions");
@@ -3518,6 +3940,8 @@ uint64_t wallet2::get_dynamic_per_kb_fee_estimate()
//----------------------------------------------------------------------------------------------------
uint64_t wallet2::get_per_kb_fee()
{
+ if(m_light_wallet)
+ return m_light_wallet_per_kb_fee;
bool use_dyn_fee = use_fork_rules(HF_VERSION_DYNAMIC_FEE, -720 * 1);
if (!use_dyn_fee)
return FEE_PER_KB;
@@ -3539,7 +3963,7 @@ int wallet2::get_fee_algorithm()
//
// this function will make multiple calls to wallet2::transfer if multiple
// transactions will be required
-std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon)
+std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon)
{
const std::vector<size_t> unused_transfers_indices = select_available_outputs_from_histogram(fake_outs_count + 1, true, true, true, trusted_daemon);
@@ -3643,10 +4067,134 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto
}
}
+
+bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const
+{
+ if (!unlocked) // don't add locked outs
+ return false;
+ if (global_index == real_index) // don't re-add real one
+ return false;
+ auto item = std::make_tuple(global_index, tx_public_key, mask);
+ if (std::find(outs.back().begin(), outs.back().end(), item) != outs.back().end()) // don't add duplicates
+ return false;
+ outs.back().push_back(item);
+ return true;
+}
+
+void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count) {
+
+ MDEBUG("LIGHTWALLET - Getting random outs");
+
+ cryptonote::COMMAND_RPC_GET_RANDOM_OUTS::request oreq;
+ cryptonote::COMMAND_RPC_GET_RANDOM_OUTS::response ores;
+
+ size_t light_wallet_requested_outputs_count = (size_t)((fake_outputs_count + 1) * 1.5 + 1);
+
+ // Amounts to ask for
+ // MyMonero api handle amounts and fees as strings
+ for(size_t idx: selected_transfers) {
+ const uint64_t ask_amount = m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount();
+ std::ostringstream amount_ss;
+ amount_ss << ask_amount;
+ oreq.amounts.push_back(amount_ss.str());
+ }
+
+ oreq.count = light_wallet_requested_outputs_count;
+ m_daemon_rpc_mutex.lock();
+ bool r = epee::net_utils::invoke_http_json("/get_random_outs", oreq, ores, m_http_client, rpc_timeout, "POST");
+ m_daemon_rpc_mutex.unlock();
+ THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_random_outs");
+ THROW_WALLET_EXCEPTION_IF(ores.amount_outs.empty() , error::wallet_internal_error, "No outputs recieved from light wallet node. Error: " + ores.Error);
+
+ // Check if we got enough outputs for each amount
+ for(auto& out: ores.amount_outs) {
+ const uint64_t out_amount = boost::lexical_cast<uint64_t>(out.amount);
+ THROW_WALLET_EXCEPTION_IF(out.outputs.size() < light_wallet_requested_outputs_count , error::wallet_internal_error, "Not enough outputs for amount: " + boost::lexical_cast<std::string>(out.amount));
+ MDEBUG(out.outputs.size() << " outputs for amount "+ boost::lexical_cast<std::string>(out.amount) + " received from light wallet node");
+ }
+
+ MDEBUG("selected transfers size: " << selected_transfers.size());
+
+ for(size_t idx: selected_transfers)
+ {
+ // Create new index
+ outs.push_back(std::vector<get_outs_entry>());
+ outs.back().reserve(fake_outputs_count + 1);
+
+ // add real output first
+ const transfer_details &td = m_transfers[idx];
+ const uint64_t amount = td.is_rct() ? 0 : td.amount();
+ outs.back().push_back(std::make_tuple(td.m_global_output_index, td.get_public_key(), rct::commit(td.amount(), td.m_mask)));
+ MDEBUG("added real output " << string_tools::pod_to_hex(td.get_public_key()));
+
+ // Even if the lightwallet server returns random outputs, we pick them randomly.
+ std::vector<size_t> order;
+ order.resize(light_wallet_requested_outputs_count);
+ for (size_t n = 0; n < order.size(); ++n)
+ order[n] = n;
+ std::shuffle(order.begin(), order.end(), std::default_random_engine(crypto::rand<unsigned>()));
+
+
+ LOG_PRINT_L2("Looking for " << (fake_outputs_count+1) << " outputs with amounts " << print_money(td.is_rct() ? 0 : td.amount()));
+ MDEBUG("OUTS SIZE: " << outs.back().size());
+ for (size_t o = 0; o < light_wallet_requested_outputs_count && outs.back().size() < fake_outputs_count + 1; ++o)
+ {
+ // Random pick
+ size_t i = order[o];
+
+ // Find which random output key to use
+ bool found_amount = false;
+ size_t amount_key;
+ for(amount_key = 0; amount_key < ores.amount_outs.size(); ++amount_key)
+ {
+ if(boost::lexical_cast<uint64_t>(ores.amount_outs[amount_key].amount) == amount) {
+ found_amount = true;
+ break;
+ }
+ }
+ THROW_WALLET_EXCEPTION_IF(!found_amount , error::wallet_internal_error, "Outputs for amount " + boost::lexical_cast<std::string>(ores.amount_outs[amount_key].amount) + " not found" );
+
+ LOG_PRINT_L2("Index " << i << "/" << light_wallet_requested_outputs_count << ": idx " << ores.amount_outs[amount_key].outputs[i].global_index << " (real " << td.m_global_output_index << "), unlocked " << "(always in light)" << ", key " << ores.amount_outs[0].outputs[i].public_key);
+
+ // Convert light wallet string data to proper data structures
+ crypto::public_key tx_public_key;
+ rct::key mask = AUTO_VAL_INIT(mask); // decrypted mask - not used here
+ rct::key rct_commit = AUTO_VAL_INIT(rct_commit);
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, ores.amount_outs[amount_key].outputs[i].public_key), error::wallet_internal_error, "Invalid public_key");
+ string_tools::hex_to_pod(ores.amount_outs[amount_key].outputs[i].public_key, tx_public_key);
+ const uint64_t global_index = ores.amount_outs[amount_key].outputs[i].global_index;
+ if(!light_wallet_parse_rct_str(ores.amount_outs[amount_key].outputs[i].rct, tx_public_key, 0, mask, rct_commit, false))
+ rct_commit = rct::zeroCommit(td.amount());
+
+ if (tx_add_fake_output(outs, global_index, tx_public_key, rct_commit, td.m_global_output_index, true)) {
+ MDEBUG("added fake output " << ores.amount_outs[amount_key].outputs[i].public_key);
+ MDEBUG("index " << global_index);
+ }
+ }
+
+ THROW_WALLET_EXCEPTION_IF(outs.back().size() < fake_outputs_count + 1 , error::wallet_internal_error, "Not enough fake outputs found" );
+
+ // Real output is the first. Shuffle outputs
+ MTRACE(outs.back().size() << " outputs added. Sorting outputs by index:");
+ std::sort(outs.back().begin(), outs.back().end(), [](const get_outs_entry &a, const get_outs_entry &b) { return std::get<0>(a) < std::get<0>(b); });
+
+ // Print output order
+ for(auto added_out: outs.back())
+ MTRACE(std::get<0>(added_out));
+
+ }
+}
+
void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count)
{
LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count);
outs.clear();
+
+ if(m_light_wallet && fake_outputs_count > 0) {
+ light_wallet_get_outs(outs, selected_transfers, fake_outputs_count);
+ return;
+ }
+
if (fake_outputs_count > 0)
{
// get histogram for the amounts we need
@@ -3843,14 +4391,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
{
size_t i = base + order[o];
LOG_PRINT_L2("Index " << i << "/" << requested_outputs_count << ": idx " << req.outputs[i].index << " (real " << td.m_global_output_index << "), unlocked " << daemon_resp.outs[i].unlocked << ", key " << daemon_resp.outs[i].key);
- if (req.outputs[i].index == td.m_global_output_index) // don't re-add real one
- continue;
- if (!daemon_resp.outs[i].unlocked) // don't add locked outs
- continue;
- auto item = std::make_tuple(req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask);
- if (std::find(outs.back().begin(), outs.back().end(), item) != outs.back().end()) // don't add duplicates
- continue;
- outs.back().push_back(item);
+ tx_add_fake_output(outs, req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask, td.m_global_output_index, daemon_resp.outs[i].unlocked);
}
if (outs.back().size() < fake_outputs_count + 1)
{
@@ -3872,14 +4413,14 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
const transfer_details &td = m_transfers[idx];
std::vector<get_outs_entry> v;
const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount());
- v.push_back(std::make_tuple(td.m_global_output_index, boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, mask));
+ v.push_back(std::make_tuple(td.m_global_output_index, td.get_public_key(), mask));
outs.push_back(v);
}
}
}
template<typename T>
-void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count,
+void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count,
std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx)
{
@@ -3910,6 +4451,10 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee));
THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee);
+ uint32_t subaddr_account = m_transfers[*selected_transfers.begin()].m_subaddr_index.major;
+ for (auto i = ++selected_transfers.begin(); i != selected_transfers.end(); ++i)
+ THROW_WALLET_EXCEPTION_IF(subaddr_account != m_transfers[*i].m_subaddr_index.major, error::wallet_internal_error, "the tx uses funds from multiple accounts");
+
if (outs.empty())
get_outs(outs, selected_transfers, fake_outputs_count); // may throw
@@ -3952,6 +4497,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
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, td.m_pk_index);
+ src.real_out_additional_tx_keys = get_additional_tx_pub_keys_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;
detail::print_source_entry(src);
@@ -3962,7 +4508,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
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.addr = get_subaddress({subaddr_account, 0});
change_dts.amount = found_money - needed_money;
}
@@ -3975,13 +4521,14 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
}
for(auto& d: dust_dsts) {
if (!dust_policy.add_to_fee)
- splitted_dsts.push_back(cryptonote::tx_destination_entry(d.amount, dust_policy.addr_for_dust));
+ splitted_dsts.push_back(cryptonote::tx_destination_entry(d.amount, dust_policy.addr_for_dust, d.is_subaddress));
dust += d.amount;
}
crypto::secret_key tx_key;
+ std::vector<crypto::secret_key> additional_tx_keys;
LOG_PRINT_L2("constructing tx");
- bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sources, splitted_dsts, extra, tx, unlock_time, tx_key);
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys);
LOG_PRINT_L2("constructed tx, r="<<r);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_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);
@@ -4009,6 +4556,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
ptx.change_dts = change_dts;
ptx.selected_transfers = selected_transfers;
ptx.tx_key = tx_key;
+ ptx.additional_tx_keys = additional_tx_keys;
ptx.dests = dsts;
ptx.construction_data.sources = sources;
ptx.construction_data.change_dts = change_dts;
@@ -4018,10 +4566,15 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
ptx.construction_data.unlock_time = unlock_time;
ptx.construction_data.use_rct = false;
ptx.construction_data.dests = dsts;
+ // record which subaddress indices are being used as inputs
+ ptx.construction_data.subaddr_account = subaddr_account;
+ ptx.construction_data.subaddr_indices.clear();
+ for (size_t idx: selected_transfers)
+ ptx.construction_data.subaddr_indices.insert(m_transfers[idx].m_subaddr_index.minor);
LOG_PRINT_L2("transfer_selected done");
}
-void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count,
+void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count,
std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx)
{
@@ -4055,6 +4608,10 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee));
THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee);
+ uint32_t subaddr_account = m_transfers[*selected_transfers.begin()].m_subaddr_index.major;
+ for (auto i = ++selected_transfers.begin(); i != selected_transfers.end(); ++i)
+ THROW_WALLET_EXCEPTION_IF(subaddr_account != m_transfers[*i].m_subaddr_index.major, error::wallet_internal_error, "the tx uses funds from multiple accounts");
+
if (outs.empty())
get_outs(outs, selected_transfers, fake_outputs_count); // may throw
@@ -4071,6 +4628,9 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
src.rct = td.is_rct();
//paste mixin transaction
+ THROW_WALLET_EXCEPTION_IF(outs.size() < out_index + 1 , error::wallet_internal_error, "outs.size() < out_index + 1");
+ THROW_WALLET_EXCEPTION_IF(outs[out_index].size() < fake_outputs_count , error::wallet_internal_error, "fake_outputs_count > random outputs found");
+
typedef cryptonote::tx_source_entry::output_entry tx_output_entry;
for (size_t n = 0; n < fake_outputs_count + 1; ++n)
{
@@ -4092,10 +4652,11 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
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.dest = rct::pk2rct(td.get_public_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, td.m_pk_index);
+ src.real_out_additional_tx_keys = get_additional_tx_pub_keys_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;
@@ -4122,13 +4683,14 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
}
else
{
- change_dts.addr = m_account.get_keys().m_account_address;
+ change_dts.addr = get_subaddress({subaddr_account, 0});
}
splitted_dsts.push_back(change_dts);
crypto::secret_key tx_key;
+ std::vector<crypto::secret_key> additional_tx_keys;
LOG_PRINT_L2("constructing tx");
- bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sources, splitted_dsts, extra, tx, unlock_time, tx_key, true);
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true);
LOG_PRINT_L2("constructed tx, r="<<r);
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);
@@ -4152,6 +4714,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
ptx.change_dts = change_dts;
ptx.selected_transfers = selected_transfers;
ptx.tx_key = tx_key;
+ ptx.additional_tx_keys = additional_tx_keys;
ptx.dests = dsts;
ptx.construction_data.sources = sources;
ptx.construction_data.change_dts = change_dts;
@@ -4161,6 +4724,11 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
ptx.construction_data.unlock_time = unlock_time;
ptx.construction_data.use_rct = true;
ptx.construction_data.dests = dsts;
+ // record which subaddress indices are being used as inputs
+ ptx.construction_data.subaddr_account = subaddr_account;
+ ptx.construction_data.subaddr_indices.clear();
+ for (size_t idx: selected_transfers)
+ ptx.construction_data.subaddr_indices.insert(m_transfers[idx].m_subaddr_index.minor);
LOG_PRINT_L2("transfer_selected_rct done");
}
@@ -4217,7 +4785,7 @@ static size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outp
return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES;
}
-std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money) const
+std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices) const
{
std::vector<size_t> picks;
float current_output_relatdness = 1.0f;
@@ -4228,7 +4796,7 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money) co
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))
+ if (!td.m_spent && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
{
LOG_PRINT_L2("We can use " << i << " alone: " << print_money(td.amount()));
picks.push_back(i);
@@ -4243,13 +4811,13 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money) co
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))
+ if (!td.m_spent && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
{
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))
+ if (!td2.m_spent && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2) && td2.m_subaddr_index == td.m_subaddr_index)
{
// update our picks if those outputs are less related than any we
// already found. If the same, don't update, and oldest suitable outputs
@@ -4331,6 +4899,445 @@ static uint32_t get_count_above(const std::vector<wallet2::transfer_details> &tr
return count;
}
+bool wallet2::light_wallet_login(bool &new_address)
+{
+ MDEBUG("Light wallet login request");
+ m_light_wallet_connected = false;
+ cryptonote::COMMAND_RPC_LOGIN::request request;
+ cryptonote::COMMAND_RPC_LOGIN::response response;
+ request.address = get_account().get_public_address_str(m_testnet);
+ request.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
+ // Always create account if it doesnt exist.
+ request.create_account = true;
+ m_daemon_rpc_mutex.lock();
+ bool connected = epee::net_utils::invoke_http_json("/login", request, response, m_http_client, rpc_timeout, "POST");
+ m_daemon_rpc_mutex.unlock();
+ // MyMonero doesn't send any status message. OpenMonero does.
+ m_light_wallet_connected = connected && (response.status.empty() || response.status == "success");
+ new_address = response.new_address;
+ MDEBUG("Status: " << response.status);
+ MDEBUG("Reason: " << response.reason);
+ MDEBUG("New wallet: " << response.new_address);
+ if(m_light_wallet_connected)
+ {
+ // Clear old data on successfull login.
+ // m_transfers.clear();
+ // m_payments.clear();
+ // m_unconfirmed_payments.clear();
+ }
+ return m_light_wallet_connected;
+}
+
+bool wallet2::light_wallet_import_wallet_request(cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::response &response)
+{
+ MDEBUG("Light wallet import wallet request");
+ cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::request oreq;
+ oreq.address = get_account().get_public_address_str(m_testnet);
+ oreq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
+ m_daemon_rpc_mutex.lock();
+ bool r = epee::net_utils::invoke_http_json("/import_wallet_request", oreq, response, m_http_client, rpc_timeout, "POST");
+ m_daemon_rpc_mutex.unlock();
+ THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "import_wallet_request");
+
+
+ return true;
+}
+
+void wallet2::light_wallet_get_unspent_outs()
+{
+ MDEBUG("Getting unspent outs");
+
+ cryptonote::COMMAND_RPC_GET_UNSPENT_OUTS::request oreq;
+ cryptonote::COMMAND_RPC_GET_UNSPENT_OUTS::response ores;
+
+ oreq.amount = "0";
+ oreq.address = get_account().get_public_address_str(m_testnet);
+ oreq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
+ // openMonero specific
+ oreq.dust_threshold = boost::lexical_cast<std::string>(::config::DEFAULT_DUST_THRESHOLD);
+ // below are required by openMonero api - but are not used.
+ oreq.mixin = 0;
+ oreq.use_dust = true;
+
+
+ m_daemon_rpc_mutex.lock();
+ bool r = epee::net_utils::invoke_http_json("/get_unspent_outs", oreq, ores, m_http_client, rpc_timeout, "POST");
+ m_daemon_rpc_mutex.unlock();
+ THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_unspent_outs");
+ THROW_WALLET_EXCEPTION_IF(ores.status == "error", error::wallet_internal_error, ores.reason);
+
+ m_light_wallet_per_kb_fee = ores.per_kb_fee;
+
+ std::unordered_map<crypto::hash,bool> transfers_txs;
+ for(const auto &t: m_transfers)
+ transfers_txs.emplace(t.m_txid,t.m_spent);
+
+ MDEBUG("FOUND " << ores.outputs.size() <<" outputs");
+
+ // return if no outputs found
+ if(ores.outputs.empty())
+ return;
+
+ // Clear old outputs
+ m_transfers.clear();
+
+ for (const auto &o: ores.outputs) {
+ bool spent = false;
+ bool add_transfer = true;
+ crypto::key_image unspent_key_image;
+ crypto::public_key tx_public_key = AUTO_VAL_INIT(tx_public_key);
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, o.tx_pub_key), error::wallet_internal_error, "Invalid tx_pub_key field");
+ string_tools::hex_to_pod(o.tx_pub_key, tx_public_key);
+
+ for (const std::string &ski: o.spend_key_images) {
+ spent = false;
+
+ // Check if key image is ours
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, ski), error::wallet_internal_error, "Invalid key image");
+ string_tools::hex_to_pod(ski, unspent_key_image);
+ if(light_wallet_key_image_is_ours(unspent_key_image, tx_public_key, o.index)){
+ MTRACE("Output " << o.public_key << " is spent. Key image: " << ski);
+ spent = true;
+ break;
+ } {
+ MTRACE("Unspent output found. " << o.public_key);
+ }
+ }
+
+ // Check if tx already exists in m_transfers.
+ crypto::hash txid;
+ crypto::public_key tx_pub_key;
+ crypto::public_key public_key;
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, o.tx_hash), error::wallet_internal_error, "Invalid tx_hash field");
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, o.public_key), error::wallet_internal_error, "Invalid public_key field");
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, o.tx_pub_key), error::wallet_internal_error, "Invalid tx_pub_key field");
+ string_tools::hex_to_pod(o.tx_hash, txid);
+ string_tools::hex_to_pod(o.public_key, public_key);
+ string_tools::hex_to_pod(o.tx_pub_key, tx_pub_key);
+
+ for(auto &t: m_transfers){
+ if(t.get_public_key() == public_key) {
+ t.m_spent = spent;
+ add_transfer = false;
+ break;
+ }
+ }
+
+ if(!add_transfer)
+ continue;
+
+ m_transfers.push_back(boost::value_initialized<transfer_details>());
+ transfer_details& td = m_transfers.back();
+
+ td.m_block_height = o.height;
+ td.m_global_output_index = o.global_index;
+ td.m_txid = txid;
+
+ // Add to extra
+ add_tx_pub_key_to_extra(td.m_tx, tx_pub_key);
+
+ td.m_key_image = unspent_key_image;
+ td.m_key_image_known = !m_watch_only;
+ td.m_amount = o.amount;
+ td.m_pk_index = 0;
+ td.m_internal_output_index = o.index;
+ td.m_spent = spent;
+
+ tx_out txout;
+ txout.target = txout_to_key(public_key);
+ txout.amount = td.m_amount;
+
+ td.m_tx.vout.resize(td.m_internal_output_index + 1);
+ td.m_tx.vout[td.m_internal_output_index] = txout;
+
+ // Add unlock time and coinbase bool got from get_address_txs api call
+ std::unordered_map<crypto::hash,address_tx>::const_iterator found = m_light_wallet_address_txs.find(txid);
+ THROW_WALLET_EXCEPTION_IF(found == m_light_wallet_address_txs.end(), error::wallet_internal_error, "Lightwallet: tx not found in m_light_wallet_address_txs");
+ bool miner_tx = found->second.m_coinbase;
+ td.m_tx.unlock_time = found->second.m_unlock_time;
+
+ if (!o.rct.empty())
+ {
+ // Coinbase tx's
+ if(miner_tx)
+ {
+ td.m_mask = rct::identity();
+ }
+ else
+ {
+ // rct txs
+ // decrypt rct mask, calculate commit hash and compare against blockchain commit hash
+ rct::key rct_commit;
+ light_wallet_parse_rct_str(o.rct, tx_pub_key, td.m_internal_output_index, td.m_mask, rct_commit, true);
+ bool valid_commit = (rct_commit == rct::commit(td.amount(), td.m_mask));
+ if(!valid_commit)
+ {
+ MDEBUG("output index: " << o.global_index);
+ MDEBUG("mask: " + string_tools::pod_to_hex(td.m_mask));
+ MDEBUG("calculated commit: " + string_tools::pod_to_hex(rct::commit(td.amount(), td.m_mask)));
+ MDEBUG("expected commit: " + string_tools::pod_to_hex(rct_commit));
+ MDEBUG("amount: " << td.amount());
+ }
+ THROW_WALLET_EXCEPTION_IF(!valid_commit, error::wallet_internal_error, "Lightwallet: rct commit hash mismatch!");
+ }
+ td.m_rct = true;
+ }
+ else
+ {
+ td.m_mask = rct::identity();
+ td.m_rct = false;
+ }
+ if(!spent)
+ set_unspent(m_transfers.size()-1);
+ m_key_images[td.m_key_image] = m_transfers.size()-1;
+ m_pub_keys[td.get_public_key()] = m_transfers.size()-1;
+ }
+}
+
+bool wallet2::light_wallet_get_address_info(cryptonote::COMMAND_RPC_GET_ADDRESS_INFO::response &response)
+{
+ MTRACE(__FUNCTION__);
+
+ cryptonote::COMMAND_RPC_GET_ADDRESS_INFO::request request;
+
+ request.address = get_account().get_public_address_str(m_testnet);
+ request.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
+ m_daemon_rpc_mutex.lock();
+ bool r = epee::net_utils::invoke_http_json("/get_address_info", request, response, m_http_client, rpc_timeout, "POST");
+ m_daemon_rpc_mutex.unlock();
+ THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_address_info");
+ // TODO: Validate result
+ return true;
+}
+
+void wallet2::light_wallet_get_address_txs()
+{
+ MDEBUG("Refreshing light wallet");
+
+ cryptonote::COMMAND_RPC_GET_ADDRESS_TXS::request ireq;
+ cryptonote::COMMAND_RPC_GET_ADDRESS_TXS::response ires;
+
+ ireq.address = get_account().get_public_address_str(m_testnet);
+ ireq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
+ m_daemon_rpc_mutex.lock();
+ bool r = epee::net_utils::invoke_http_json("/get_address_txs", ireq, ires, m_http_client, rpc_timeout, "POST");
+ m_daemon_rpc_mutex.unlock();
+ THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_address_txs");
+ //OpenMonero sends status=success, Mymonero doesn't.
+ THROW_WALLET_EXCEPTION_IF((!ires.status.empty() && ires.status != "success"), error::no_connection_to_daemon, "get_address_txs");
+
+
+ // Abort if no transactions
+ if(ires.transactions.empty())
+ return;
+
+ // Create searchable vectors
+ std::vector<crypto::hash> payments_txs;
+ for(const auto &p: m_payments)
+ payments_txs.push_back(p.second.m_tx_hash);
+ std::vector<crypto::hash> unconfirmed_payments_txs;
+ for(const auto &up: m_unconfirmed_payments)
+ unconfirmed_payments_txs.push_back(up.second.m_tx_hash);
+
+ // for balance calculation
+ uint64_t wallet_total_sent = 0;
+ uint64_t wallet_total_unlocked_sent = 0;
+ // txs in pool
+ std::vector<crypto::hash> pool_txs;
+
+ for (const auto &t: ires.transactions) {
+ const uint64_t total_received = t.total_received;
+ uint64_t total_sent = t.total_sent;
+
+ // Check key images - subtract fake outputs from total_sent
+ for(const auto &so: t.spent_outputs)
+ {
+ crypto::public_key tx_public_key;
+ crypto::key_image key_image;
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, so.tx_pub_key), error::wallet_internal_error, "Invalid tx_pub_key field");
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, so.key_image), error::wallet_internal_error, "Invalid key_image field");
+ string_tools::hex_to_pod(so.tx_pub_key, tx_public_key);
+ string_tools::hex_to_pod(so.key_image, key_image);
+
+ if(!light_wallet_key_image_is_ours(key_image, tx_public_key, so.out_index)) {
+ THROW_WALLET_EXCEPTION_IF(so.amount > t.total_sent, error::wallet_internal_error, "Lightwallet: total sent is negative!");
+ total_sent -= so.amount;
+ }
+ }
+
+ // Do not add tx if empty.
+ if(total_sent == 0 && total_received == 0)
+ continue;
+
+ crypto::hash payment_id = null_hash;
+ crypto::hash tx_hash;
+
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, t.payment_id), error::wallet_internal_error, "Invalid payment_id field");
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, t.hash), error::wallet_internal_error, "Invalid hash field");
+ string_tools::hex_to_pod(t.payment_id, payment_id);
+ string_tools::hex_to_pod(t.hash, tx_hash);
+
+ // lightwallet specific info
+ bool incoming = (total_received > total_sent);
+ address_tx address_tx;
+ address_tx.m_tx_hash = tx_hash;
+ address_tx.m_incoming = incoming;
+ address_tx.m_amount = incoming ? total_received - total_sent : total_sent - total_received;
+ address_tx.m_block_height = t.height;
+ address_tx.m_unlock_time = t.unlock_time;
+ address_tx.m_timestamp = t.timestamp;
+ address_tx.m_coinbase = t.coinbase;
+ address_tx.m_mempool = t.mempool;
+ m_light_wallet_address_txs.emplace(tx_hash,address_tx);
+
+ // populate data needed for history (m_payments, m_unconfirmed_payments, m_confirmed_txs)
+ // INCOMING transfers
+ if(total_received > total_sent) {
+ payment_details payment;
+ payment.m_tx_hash = tx_hash;
+ payment.m_amount = total_received - total_sent;
+ payment.m_block_height = t.height;
+ payment.m_unlock_time = t.unlock_time;
+ payment.m_timestamp = t.timestamp;
+
+ if (t.mempool) {
+ if (std::find(unconfirmed_payments_txs.begin(), unconfirmed_payments_txs.end(), tx_hash) == unconfirmed_payments_txs.end()) {
+ pool_txs.push_back(tx_hash);
+ m_unconfirmed_payments.emplace(tx_hash, payment);
+ if (0 != m_callback) {
+ m_callback->on_lw_unconfirmed_money_received(t.height, payment.m_tx_hash, payment.m_amount);
+ }
+ }
+ } else {
+ if (std::find(payments_txs.begin(), payments_txs.end(), tx_hash) == payments_txs.end()) {
+ m_payments.emplace(tx_hash, payment);
+ if (0 != m_callback) {
+ m_callback->on_lw_money_received(t.height, payment.m_tx_hash, payment.m_amount);
+ }
+ }
+ }
+ // Outgoing transfers
+ } else {
+ uint64_t amount_sent = total_sent - total_received;
+ cryptonote::transaction dummy_tx; // not used by light wallet
+ // increase wallet total sent
+ wallet_total_sent += total_sent;
+ if (t.mempool)
+ {
+ // Handled by add_unconfirmed_tx in commit_tx
+ // If sent from another wallet instance we need to add it
+ if(m_unconfirmed_txs.find(tx_hash) == m_unconfirmed_txs.end())
+ {
+ unconfirmed_transfer_details utd;
+ utd.m_amount_in = amount_sent;
+ utd.m_amount_out = amount_sent;
+ utd.m_change = 0;
+ utd.m_payment_id = payment_id;
+ utd.m_timestamp = t.timestamp;
+ utd.m_state = wallet2::unconfirmed_transfer_details::pending;
+ m_unconfirmed_txs.emplace(tx_hash,utd);
+ }
+ }
+ else
+ {
+ // Only add if new
+ auto confirmed_tx = m_confirmed_txs.find(tx_hash);
+ if(confirmed_tx == m_confirmed_txs.end()) {
+ // tx is added to m_unconfirmed_txs - move to confirmed
+ if(m_unconfirmed_txs.find(tx_hash) != m_unconfirmed_txs.end())
+ {
+ process_unconfirmed(tx_hash, dummy_tx, t.height);
+ }
+ // Tx sent by another wallet instance
+ else
+ {
+ confirmed_transfer_details ctd;
+ ctd.m_amount_in = amount_sent;
+ ctd.m_amount_out = amount_sent;
+ ctd.m_change = 0;
+ ctd.m_payment_id = payment_id;
+ ctd.m_block_height = t.height;
+ ctd.m_timestamp = t.timestamp;
+ m_confirmed_txs.emplace(tx_hash,ctd);
+ }
+ if (0 != m_callback)
+ {
+ m_callback->on_lw_money_spent(t.height, tx_hash, amount_sent);
+ }
+ }
+ // If not new - check the amount and update if necessary.
+ // when sending a tx to same wallet the receiving amount has to be credited
+ else
+ {
+ if(confirmed_tx->second.m_amount_in != amount_sent || confirmed_tx->second.m_amount_out != amount_sent)
+ {
+ MDEBUG("Adjusting amount sent/received for tx: <" + t.hash + ">. Is tx sent to own wallet? " << print_money(amount_sent) << " != " << print_money(confirmed_tx->second.m_amount_in));
+ confirmed_tx->second.m_amount_in = amount_sent;
+ confirmed_tx->second.m_amount_out = amount_sent;
+ confirmed_tx->second.m_change = 0;
+ }
+ }
+ }
+ }
+ }
+ // TODO: purge old unconfirmed_txs
+ remove_obsolete_pool_txs(pool_txs);
+
+ // Calculate wallet balance
+ m_light_wallet_balance = ires.total_received-wallet_total_sent;
+ // MyMonero doesnt send unlocked balance
+ if(ires.total_received_unlocked > 0)
+ m_light_wallet_unlocked_balance = ires.total_received_unlocked-wallet_total_sent;
+ else
+ m_light_wallet_unlocked_balance = m_light_wallet_balance;
+}
+
+bool wallet2::light_wallet_parse_rct_str(const std::string& rct_string, const crypto::public_key& tx_pub_key, uint64_t internal_output_index, rct::key& decrypted_mask, rct::key& rct_commit, bool decrypt) const
+{
+ // rct string is empty if output is non RCT
+ if (rct_string.empty())
+ return false;
+ // rct_string is a string with length 64+64+64 (<rct commit> + <encrypted mask> + <rct amount>)
+ rct::key encrypted_mask;
+ std::string rct_commit_str = rct_string.substr(0,64);
+ std::string encrypted_mask_str = rct_string.substr(64,64);
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, rct_commit_str), error::wallet_internal_error, "Invalid rct commit hash: " + rct_commit_str);
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, encrypted_mask_str), error::wallet_internal_error, "Invalid rct mask: " + encrypted_mask_str);
+ string_tools::hex_to_pod(rct_commit_str, rct_commit);
+ string_tools::hex_to_pod(encrypted_mask_str, encrypted_mask);
+ if (decrypt) {
+ // Decrypt the mask
+ crypto::key_derivation derivation;
+ generate_key_derivation(tx_pub_key, get_account().get_keys().m_view_secret_key, derivation);
+ crypto::secret_key scalar;
+ crypto::derivation_to_scalar(derivation, internal_output_index, scalar);
+ sc_sub(decrypted_mask.bytes,encrypted_mask.bytes,rct::hash_to_scalar(rct::sk2rct(scalar)).bytes);
+ }
+ return true;
+}
+
+bool wallet2::light_wallet_key_image_is_ours(const crypto::key_image& key_image, const crypto::public_key& tx_public_key, uint64_t out_index)
+{
+ // Lookup key image from cache
+ std::map<uint64_t, crypto::key_image> index_keyimage_map;
+ std::unordered_map<crypto::public_key, std::map<uint64_t, crypto::key_image> >::const_iterator found_pub_key = m_key_image_cache.find(tx_public_key);
+ if(found_pub_key != m_key_image_cache.end()) {
+ // pub key found. key image for index cached?
+ index_keyimage_map = found_pub_key->second;
+ std::map<uint64_t,crypto::key_image>::const_iterator index_found = index_keyimage_map.find(out_index);
+ if(index_found != index_keyimage_map.end())
+ return key_image == index_found->second;
+ }
+
+ // Not in cache - calculate key image
+ crypto::key_image calculated_key_image;
+ cryptonote::keypair in_ephemeral;
+ cryptonote::generate_key_image_helper(get_account().get_keys(), tx_public_key, out_index, in_ephemeral, calculated_key_image);
+ index_keyimage_map.emplace(out_index, calculated_key_image);
+ m_key_image_cache.emplace(tx_public_key, index_keyimage_map);
+ return key_image == calculated_key_image;
+}
+
// 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
@@ -4346,10 +5353,14 @@ static uint32_t get_count_above(const std::vector<wallet2::transfer_details> &tr
// This system allows for sending (almost) the entire balance, since it does
// not generate spurious change in all txes, thus decreasing the instantaneous
// usable balance.
-std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon)
+std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon)
{
- std::vector<size_t> unused_transfers_indices;
- std::vector<size_t> unused_dust_indices;
+ if(m_light_wallet) {
+ // Populate m_transfers
+ light_wallet_get_unspent_outs();
+ }
+ std::vector<std::pair<uint32_t, std::vector<size_t>>> unused_transfers_indices_per_subaddr;
+ std::vector<std::pair<uint32_t, std::vector<size_t>>> unused_dust_indices_per_subaddr;
uint64_t needed_money;
uint64_t accumulated_fee, accumulated_outputs, accumulated_change;
struct TX {
@@ -4359,14 +5370,14 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
pending_tx ptx;
size_t bytes;
- void add(const account_public_address &addr, uint64_t amount, unsigned int original_output_index, bool merge_destinations) {
+ void add(const account_public_address &addr, bool is_subaddress, uint64_t amount, unsigned int original_output_index, bool merge_destinations) {
if (merge_destinations)
{
std::vector<cryptonote::tx_destination_entry>::iterator i;
i = std::find_if(dsts.begin(), dsts.end(), [&](const cryptonote::tx_destination_entry &d) { return !memcmp (&d.addr, &addr, sizeof(addr)); });
if (i == dsts.end())
{
- dsts.push_back(tx_destination_entry(0,addr));
+ dsts.push_back(tx_destination_entry(0,addr,is_subaddress));
i = dsts.end() - 1;
}
i->amount += amount;
@@ -4376,7 +5387,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
THROW_WALLET_EXCEPTION_IF(original_output_index > dsts.size(), error::wallet_internal_error,
std::string("original_output_index too large: ") + std::to_string(original_output_index) + " > " + std::to_string(dsts.size()));
if (original_output_index == dsts.size())
- dsts.push_back(tx_destination_entry(0,addr));
+ dsts.push_back(tx_destination_entry(0,addr,is_subaddress));
THROW_WALLET_EXCEPTION_IF(memcmp(&dsts[original_output_index].addr, &addr, sizeof(addr)), error::wallet_internal_error, "Mismatched destination address");
dsts[original_output_index].amount += amount;
}
@@ -4408,29 +5419,90 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// throw if attempting a transaction with no money
THROW_WALLET_EXCEPTION_IF(needed_money == 0, error::zero_destination);
- // gather all our dust and non dust outputs
+ std::map<uint32_t, uint64_t> balance_per_subaddr = unlocked_balance_per_subaddress(subaddr_account);
+
+ if (subaddr_indices.empty()) // "index=<N1>[,<N2>,...]" wasn't specified -> use all the indices with non-zero unlocked bakance
+ {
+ for (const auto& i : balance_per_subaddr)
+ subaddr_indices.insert(i.first);
+ }
+
+ // 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
+ uint64_t balance_subtotal = 0;
+ for (uint32_t index_minor : subaddr_indices)
+ balance_subtotal += balance_per_subaddr[index_minor];
+ THROW_WALLET_EXCEPTION_IF(needed_money > balance_subtotal, error::not_enough_money,
+ balance_subtotal, needed_money, 0);
+
+ for (uint32_t i : subaddr_indices)
+ LOG_PRINT_L2("Candidate subaddress index for spending: " << i);
+
+ // gather all dust and non-dust outputs belonging to specified subaddresses
+ size_t num_nondust_outputs = 0;
+ size_t num_dust_outputs = 0;
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
- if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td))
+ if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
{
+ const uint32_t index_minor = td.m_subaddr_index.minor;
+ auto find_predicate = [&index_minor](const std::pair<uint32_t, std::vector<size_t>>& x) { return x.first == index_minor; };
if ((td.is_rct()) || is_valid_decomposed_amount(td.amount()))
- unused_transfers_indices.push_back(i);
+ {
+ auto found = std::find_if(unused_transfers_indices_per_subaddr.begin(), unused_transfers_indices_per_subaddr.end(), find_predicate);
+ if (found == unused_transfers_indices_per_subaddr.end())
+ {
+ unused_transfers_indices_per_subaddr.push_back({index_minor, {i}});
+ }
+ else
+ {
+ found->second.push_back(i);
+ }
+ ++num_nondust_outputs;
+ }
else
- unused_dust_indices.push_back(i);
+ {
+ auto found = std::find_if(unused_dust_indices_per_subaddr.begin(), unused_dust_indices_per_subaddr.end(), find_predicate);
+ if (found == unused_dust_indices_per_subaddr.end())
+ {
+ unused_dust_indices_per_subaddr.push_back({index_minor, {i}});
+ }
+ else
+ {
+ found->second.push_back(i);
+ }
+ ++num_dust_outputs;
+ }
}
}
- 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);
+ // shuffle & sort output indices
+ {
+ std::random_device rd;
+ std::mt19937 g(rd());
+ std::shuffle(unused_transfers_indices_per_subaddr.begin(), unused_transfers_indices_per_subaddr.end(), g);
+ std::shuffle(unused_dust_indices_per_subaddr.begin(), unused_dust_indices_per_subaddr.end(), g);
+ auto sort_predicate = [&balance_per_subaddr] (const std::pair<uint32_t, std::vector<size_t>>& x, const std::pair<uint32_t, std::vector<size_t>>& y)
+ {
+ return balance_per_subaddr[x.first] > balance_per_subaddr[y.first];
+ };
+ std::sort(unused_transfers_indices_per_subaddr.begin(), unused_transfers_indices_per_subaddr.end(), sort_predicate);
+ std::sort(unused_dust_indices_per_subaddr.begin(), unused_dust_indices_per_subaddr.end(), sort_predicate);
+ }
- if (unused_dust_indices.empty() && unused_transfers_indices.empty())
+ LOG_PRINT_L2("Starting with " << num_nondust_outputs << " non-dust outputs and " << num_dust_outputs << " dust outputs");
+
+ if (unused_dust_indices_per_subaddr.empty() && unused_transfers_indices_per_subaddr.empty())
return std::vector<wallet2::pending_tx>();
+ // if empty, put dummy entry so that the front can be referenced later in the loop
+ if (unused_dust_indices_per_subaddr.empty())
+ unused_dust_indices_per_subaddr.push_back({});
+ if (unused_transfers_indices_per_subaddr.empty())
+ unused_transfers_indices_per_subaddr.push_back({});
+
// start with an empty tx
txes.push_back(TX());
accumulated_fee = 0;
@@ -4449,17 +5521,36 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
std::vector<size_t> preferred_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)
+ if (use_rct)
{
// 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(fee_per_kb, estimate_rct_tx_size(2, fake_outs_count + 1, 2), fee_multiplier);
- preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee);
+ preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices);
if (!preferred_inputs.empty())
{
string s;
for (auto i: preferred_inputs) s += boost::lexical_cast<std::string>(i) + "(" + print_money(m_transfers[i].amount()) + ") ";
- LOG_PRINT_L1("Found preferred rct inputs for rct tx: " << s);
+ LOG_PRINT_L1("Found prefered rct inputs for rct tx: " << s);
+
+ // bring the list of available outputs stored by the same subaddress index to the front of the list
+ uint32_t index_minor = m_transfers[preferred_inputs[0]].m_subaddr_index.minor;
+ for (size_t i = 1; i < unused_transfers_indices_per_subaddr.size(); ++i)
+ {
+ if (unused_transfers_indices_per_subaddr[i].first == index_minor)
+ {
+ std::swap(unused_transfers_indices_per_subaddr[0], unused_transfers_indices_per_subaddr[i]);
+ break;
+ }
+ }
+ for (size_t i = 1; i < unused_dust_indices_per_subaddr.size(); ++i)
+ {
+ if (unused_dust_indices_per_subaddr[i].first == index_minor)
+ {
+ std::swap(unused_dust_indices_per_subaddr[0], unused_dust_indices_per_subaddr[i]);
+ break;
+ }
+ }
}
}
LOG_PRINT_L2("done checking preferred");
@@ -4469,19 +5560,22 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// - or we need to gather more fee
// - or we have just one input in that tx, which is rct (to try and make all/most rct txes 2/2)
unsigned int original_output_index = 0;
- while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee || should_pick_a_second_output(use_rct, txes.back().selected_transfers.size(), unused_transfers_indices, unused_dust_indices)) {
+ std::vector<size_t>* unused_transfers_indices = &unused_transfers_indices_per_subaddr[0].second;
+ std::vector<size_t>* unused_dust_indices = &unused_dust_indices_per_subaddr[0].second;
+ while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee || should_pick_a_second_output(use_rct, txes.back().selected_transfers.size(), *unused_transfers_indices, *unused_dust_indices)) {
TX &tx = txes.back();
- LOG_PRINT_L2("Start of loop with " << unused_transfers_indices.size() << " " << unused_dust_indices.size());
- LOG_PRINT_L2("unused_transfers_indices: " << strjoin(unused_transfers_indices, " "));
- LOG_PRINT_L2("unused_dust_indices:" << strjoin(unused_dust_indices, " "));
+ LOG_PRINT_L2("Start of loop with " << unused_transfers_indices->size() << " " << unused_dust_indices->size());
+ LOG_PRINT_L2("Start of loop with " << unused_transfers_indices->size() << " " << unused_dust_indices->size());
+ LOG_PRINT_L2("unused_transfers_indices: " << strjoin(*unused_transfers_indices, " "));
+ LOG_PRINT_L2("unused_dust_indices:" << strjoin(*unused_dust_indices, " "));
LOG_PRINT_L2("dsts size " << dsts.size() << ", first " << (dsts.empty() ? -1 : dsts[0].amount));
LOG_PRINT_L2("adding_fee " << adding_fee << ", use_rct " << use_rct);
// if we need to spend money and don't have any left, we fail
- if (unused_dust_indices.empty() && unused_transfers_indices.empty()) {
+ if (unused_dust_indices->empty() && unused_transfers_indices->empty()) {
LOG_PRINT_L2("No more outputs to choose from");
- THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(), needed_money, accumulated_fee + needed_fee);
+ THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(subaddr_account), 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)
@@ -4489,12 +5583,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
size_t idx;
if ((dsts.empty() || dsts[0].amount == 0) && !adding_fee) {
// the "make rct txes 2/2" case - we pick a small value output to "clean up" the wallet too
- std::vector<size_t> indices = get_only_rct(unused_dust_indices, unused_transfers_indices);
+ std::vector<size_t> indices = get_only_rct(*unused_dust_indices, *unused_transfers_indices);
idx = pop_best_value(indices, tx.selected_transfers, true);
// we might not want to add it if it's a large output and we don't have many left
if (m_transfers[idx].amount() >= m_min_output_value) {
- if (get_count_above(m_transfers, unused_transfers_indices, m_min_output_value) < m_min_output_count) {
+ if (get_count_above(m_transfers, *unused_transfers_indices, m_min_output_value) < m_min_output_count) {
LOG_PRINT_L2("Second output was not strictly needed, and we're running out of outputs above " << print_money(m_min_output_value) << ", not adding");
break;
}
@@ -4508,14 +5602,14 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
LOG_PRINT_L2("Second output was not strictly needed, and relatedness " << relatedness << ", not adding");
break;
}
- pop_if_present(unused_transfers_indices, idx);
- pop_if_present(unused_dust_indices, idx);
+ pop_if_present(*unused_transfers_indices, idx);
+ pop_if_present(*unused_dust_indices, idx);
} else if (!preferred_inputs.empty()) {
idx = pop_back(preferred_inputs);
- pop_if_present(unused_transfers_indices, idx);
- pop_if_present(unused_dust_indices, idx);
+ pop_if_present(*unused_transfers_indices, idx);
+ pop_if_present(*unused_dust_indices, idx);
} else
- idx = pop_best_value(unused_transfers_indices.empty() ? unused_dust_indices : unused_transfers_indices, tx.selected_transfers);
+ idx = pop_best_value(unused_transfers_indices->empty() ? *unused_dust_indices : *unused_transfers_indices, tx.selected_transfers);
const transfer_details &td = m_transfers[idx];
LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount()) << ", ki " << td.m_key_image);
@@ -4538,9 +5632,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()) < TX_SIZE_TARGET(upper_transaction_size_limit))
{
// we can fully pay that destination
- LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_testnet, dsts[0].addr) <<
+ LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_testnet, dsts[0].is_subaddress, dsts[0].addr) <<
" for " << print_money(dsts[0].amount));
- tx.add(dsts[0].addr, dsts[0].amount, original_output_index, m_merge_destinations);
+ tx.add(dsts[0].addr, dsts[0].is_subaddress, dsts[0].amount, original_output_index, m_merge_destinations);
available_amount -= dsts[0].amount;
dsts[0].amount = 0;
pop_index(dsts, 0);
@@ -4549,9 +5643,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
if (available_amount > 0 && !dsts.empty() && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()) < TX_SIZE_TARGET(upper_transaction_size_limit)) {
// we can partially fill that destination
- LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_testnet, dsts[0].addr) <<
+ LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_testnet, dsts[0].is_subaddress, dsts[0].addr) <<
" for " << print_money(available_amount) << "/" << print_money(dsts[0].amount));
- tx.add(dsts[0].addr, available_amount, original_output_index, m_merge_destinations);
+ tx.add(dsts[0].addr, dsts[0].is_subaddress, available_amount, original_output_index, m_merge_destinations);
dsts[0].amount -= available_amount;
available_amount = 0;
}
@@ -4603,7 +5697,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
if (i->amount > needed_fee)
{
uint64_t new_paid_amount = i->amount /*+ test_ptx.fee*/ - needed_fee;
- LOG_PRINT_L2("Adjusting amount paid to " << get_account_address_as_str(m_testnet, i->addr) << " from " <<
+ LOG_PRINT_L2("Adjusting amount paid to " << get_account_address_as_str(m_testnet, i->is_subaddress, i->addr) << " from " <<
print_money(i->amount) << " to " << print_money(new_paid_amount) << " to accommodate " <<
print_money(needed_fee) << " fee");
dsts[0].amount += i->amount - new_paid_amount;
@@ -4652,12 +5746,28 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
}
}
}
+
+ // if unused_*_indices is empty while unused_*_indices_per_subaddr has multiple elements, and if we still have something to pay,
+ // pop front of unused_*_indices_per_subaddr and have unused_*_indices point to the front of unused_*_indices_per_subaddr
+ if ((!dsts.empty() && dsts[0].amount > 0) || adding_fee)
+ {
+ if (unused_transfers_indices->empty() && unused_transfers_indices_per_subaddr.size() > 1)
+ {
+ unused_transfers_indices_per_subaddr.erase(unused_transfers_indices_per_subaddr.begin());
+ unused_transfers_indices = &unused_transfers_indices_per_subaddr[0].second;
+ }
+ if (unused_dust_indices->empty() && unused_dust_indices_per_subaddr.size() > 1)
+ {
+ unused_dust_indices_per_subaddr.erase(unused_dust_indices_per_subaddr.begin());
+ unused_dust_indices = &unused_dust_indices_per_subaddr[0].second;
+ }
+ }
}
if (adding_fee)
{
LOG_PRINT_L1("We ran out of outputs while trying to gather final fee");
- THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(), needed_money, accumulated_fee + needed_fee);
+ THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(subaddr_account), needed_money, accumulated_fee + needed_fee);
}
LOG_PRINT_L1("Done creating " << txes.size() << " transactions, " << print_money(accumulated_fee) <<
@@ -4681,21 +5791,37 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
return ptx_vector;
}
-std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon)
+std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon)
{
std::vector<size_t> unused_transfers_indices;
std::vector<size_t> unused_dust_indices;
const bool use_rct = use_fork_rules(4, 0);
- // gather all our dust and non dust outputs
+ THROW_WALLET_EXCEPTION_IF(unlocked_balance(subaddr_account) == 0, error::wallet_internal_error, "No unclocked balance in the entire wallet");
+
+ std::map<uint32_t, uint64_t> balance_per_subaddr = unlocked_balance_per_subaddress(subaddr_account);
+
+ if (subaddr_indices.empty())
+ {
+ // in case subaddress index wasn't specified, choose non-empty subaddress randomly (with index=0 being chosen last)
+ if (balance_per_subaddr.count(0) == 1 && balance_per_subaddr.size() > 1)
+ balance_per_subaddr.erase(0);
+ auto i = balance_per_subaddr.begin();
+ std::advance(i, crypto::rand<size_t>() % balance_per_subaddr.size());
+ subaddr_indices.insert(i->first);
+ }
+ for (uint32_t i : subaddr_indices)
+ LOG_PRINT_L2("Spending from subaddress index " << i);
+
+ // gather all dust and non-dust outputs of specified subaddress
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
- if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td))
+ if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
{
if (below == 0 || td.amount() < below)
{
- if (td.is_rct() || 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);
@@ -4703,10 +5829,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below
}
}
- return create_transactions_from(address, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra, trusted_daemon);
+ THROW_WALLET_EXCEPTION_IF(unused_transfers_indices.empty() && unused_dust_indices.empty(), error::not_enough_money, 0, 0, 0); // not sure if a new error class (something like 'cant_sweep_empty'?) should be introduced
+
+ return create_transactions_from(address, is_subaddress, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra, trusted_daemon);
}
-std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const cryptonote::account_public_address &address, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon)
+std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon)
{
uint64_t accumulated_fee, accumulated_outputs, accumulated_change;
struct TX {
@@ -4769,7 +5897,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
needed_fee = 0;
- tx.dsts.push_back(tx_destination_entry(1, address));
+ tx.dsts.push_back(tx_destination_entry(1, address, is_subaddress));
LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " <<
tx.selected_transfers.size() << " outputs");
@@ -4791,7 +5919,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
LOG_PRINT_L2("We made a tx, adjusting fee and saving it");
tx.dsts[0].amount = available_for_fee - needed_fee;
if (use_rct)
- transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
+ transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
test_tx, test_ptx);
else
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
@@ -4838,21 +5966,6 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
// if we made it this far, we're OK to actually send the transactions
return ptx_vector;
}
-
-uint64_t wallet2::unlocked_dust_balance(const tx_dust_policy &dust_policy) const
-{
- uint64_t money = 0;
- std::list<transfer_container::iterator> selected_transfers;
- for (transfer_container::const_iterator i = m_transfers.begin(); i != m_transfers.end(); ++i)
- {
- const transfer_details& td = *i;
- if (!td.m_spent && td.amount() < dust_policy.dust_threshold && is_transfer_unlocked(td))
- {
- money += td.amount();
- }
- }
- return money;
-}
//----------------------------------------------------------------------------------------------------
void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height)
{
@@ -4862,6 +5975,9 @@ void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height)
//----------------------------------------------------------------------------------------------------
bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks)
{
+ // TODO: How to get fork rule info from light wallet node?
+ if(m_light_wallet)
+ return true;
uint64_t height, earliest_height;
boost::optional<std::string> result = m_node_rpc_proxy.get_height(height);
throw_on_rpc_response_error(result, "get_info");
@@ -5027,15 +6143,19 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo
unmixable_transfer_outputs.push_back(n);
}
- return create_transactions_from(m_account_public_address, unmixable_transfer_outputs, unmixable_dust_outputs, 0 /*fake_outs_count */, 0 /* unlock_time */, 1 /*priority */, std::vector<uint8_t>(), trusted_daemon);
+ return create_transactions_from(m_account_public_address, false, unmixable_transfer_outputs, unmixable_dust_outputs, 0 /*fake_outs_count */, 0 /* unlock_time */, 1 /*priority */, std::vector<uint8_t>(), trusted_daemon);
}
-bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) const
+bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const
{
+ additional_tx_keys.clear();
const std::unordered_map<crypto::hash, crypto::secret_key>::const_iterator i = m_tx_keys.find(txid);
if (i == m_tx_keys.end())
return false;
tx_key = i->second;
+ const auto j = m_additional_tx_keys.find(txid);
+ if (j != m_additional_tx_keys.end())
+ additional_tx_keys = j->second;
return true;
}
@@ -5190,6 +6310,15 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle
// more than one, loop and search
const cryptonote::account_keys& keys = m_account.get_keys();
size_t pk_index = 0;
+
+ const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx);
+ std::vector<crypto::key_derivation> additional_derivations;
+ for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i)
+ {
+ additional_derivations.push_back({});
+ generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back());
+ }
+
while (find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, pk_index++)) {
const crypto::public_key tx_pub_key = pub_key_field.pub_key;
crypto::key_derivation derivation;
@@ -5198,7 +6327,7 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle
for (size_t i = 0; i < td.m_tx.vout.size(); ++i)
{
tx_scan_info_t tx_scan_info;
- check_acc_out_precomp(keys.m_account_address.m_spend_public_key, td.m_tx.vout[i], derivation, i, tx_scan_info);
+ check_acc_out_precomp(td.m_tx.vout[i], derivation, additional_derivations, i, tx_scan_info);
if (!tx_scan_info.error && tx_scan_info.received)
return tx_pub_key;
}
@@ -5210,7 +6339,7 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle
return crypto::null_pkey;
}
-bool wallet2::export_key_images(const std::string filename)
+bool wallet2::export_key_images(const std::string &filename)
{
std::vector<std::pair<crypto::key_image, crypto::signature>> ski = export_key_images();
std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC));
@@ -5258,11 +6387,12 @@ std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key
}
crypto::public_key tx_pub_key = get_tx_pub_key_from_received_outs(td);
+ const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx);
// generate ephemeral secret key
crypto::key_image ki;
cryptonote::keypair in_ephemeral;
- bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, td.m_internal_output_index, in_ephemeral, ki);
+ bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, pkey, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, ki);
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image");
THROW_WALLET_EXCEPTION_IF(td.m_key_image_known && ki != td.m_key_image,
@@ -5492,18 +6622,25 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(spent_tx);
crypto::key_derivation derivation;
generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation);
+ const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(spent_tx);
+ std::vector<crypto::key_derivation> additional_derivations;
+ for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i)
+ {
+ additional_derivations.push_back({});
+ generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back());
+ }
size_t output_index = 0;
for (const cryptonote::tx_out& out : spent_tx.vout)
{
tx_scan_info_t tx_scan_info;
- check_acc_out_precomp(keys.m_account_address.m_spend_public_key, out, derivation, output_index, tx_scan_info);
+ check_acc_out_precomp(out, derivation, additional_derivations, output_index, tx_scan_info);
THROW_WALLET_EXCEPTION_IF(tx_scan_info.error, error::wallet_internal_error, "check_acc_out_precomp failed");
if (tx_scan_info.received)
{
if (tx_scan_info.money_transfered == 0)
{
rct::key mask;
- tx_scan_info.money_transfered = tools::decodeRct(spent_tx.rct_signatures, tx_pub_key, keys.m_view_secret_key, output_index, mask);
+ tx_scan_info.money_transfered = tools::decodeRct(spent_tx.rct_signatures, tx_scan_info.received->derivation, output_index, mask);
}
tx_money_got_in_outs += tx_scan_info.money_transfered;
}
@@ -5512,6 +6649,8 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
// get spent amount
uint64_t tx_money_spent_in_ins = 0;
+ uint32_t subaddr_account = (uint32_t)-1;
+ std::set<uint32_t> subaddr_indices;
for (const cryptonote::txin_v& in : spent_tx.vin)
{
if (in.type() != typeid(cryptonote::txin_to_key))
@@ -5533,12 +6672,16 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
LOG_PRINT_L0("Spent money: " << print_money(amount) << ", with tx: " << *spent_txid);
set_spent(it->second, e.block_height);
if (m_callback)
- m_callback->on_money_spent(e.block_height, *spent_txid, spent_tx, amount, spent_tx);
+ m_callback->on_money_spent(e.block_height, *spent_txid, spent_tx, amount, spent_tx, td.m_subaddr_index);
+ if (subaddr_account != (uint32_t)-1 && subaddr_account != td.m_subaddr_index.major)
+ LOG_PRINT_L0("WARNING: This tx spends outputs received by different subaddress accounts, which isn't supposed to happen");
+ subaddr_account = td.m_subaddr_index.major;
+ subaddr_indices.insert(td.m_subaddr_index.minor);
}
}
// create outgoing payment
- process_outgoing(*spent_txid, spent_tx, e.block_height, e.block_timestamp, tx_money_spent_in_ins, tx_money_got_in_outs);
+ process_outgoing(*spent_txid, spent_tx, e.block_height, e.block_timestamp, tx_money_spent_in_ins, tx_money_got_in_outs, subaddr_account, subaddr_indices);
// erase corresponding incoming payment
for (auto j = m_payments.begin(); j != m_payments.end(); ++j)
@@ -5652,15 +6795,17 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail
// the hot wallet wouldn't have known about key images (except if we already exported them)
cryptonote::keypair in_ephemeral;
std::vector<tx_extra_field> tx_extra_fields;
- tx_extra_pub_key pub_key_field;
THROW_WALLET_EXCEPTION_IF(td.m_tx.vout.empty(), error::wallet_internal_error, "tx with no outputs at index " + boost::lexical_cast<std::string>(i));
THROW_WALLET_EXCEPTION_IF(!parse_tx_extra(td.m_tx.extra, tx_extra_fields), error::wallet_internal_error,
"Transaction extra has unsupported format at index " + boost::lexical_cast<std::string>(i));
crypto::public_key tx_pub_key = get_tx_pub_key_from_received_outs(td);
+ const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx);
- bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, td.m_internal_output_index, in_ephemeral, td.m_key_image);
+ const crypto::public_key& out_key = boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key;
+ bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, out_key, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, td.m_key_image);
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image");
+ expand_subaddresses(td.m_subaddr_index);
td.m_key_image_known = true;
THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key,
error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + boost::lexical_cast<std::string>(i));
@@ -5731,17 +6876,15 @@ std::string wallet2::decrypt_with_view_secret_key(const std::string &ciphertext,
//----------------------------------------------------------------------------------------------------
std::string wallet2::make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name, std::string &error)
{
- cryptonote::account_public_address tmp_address;
- bool has_payment_id;
- crypto::hash8 new_payment_id;
- if(!get_account_integrated_address_from_str(tmp_address, has_payment_id, new_payment_id, testnet(), address))
+ cryptonote::address_parse_info info;
+ if(!get_account_address_from_str(info, testnet(), address))
{
error = std::string("wrong address: ") + address;
return std::string();
}
// we want only one payment id
- if (has_payment_id && !payment_id.empty())
+ if (info.has_payment_id && !payment_id.empty())
{
error = "A single payment id is allowed";
return std::string();
@@ -5797,10 +6940,8 @@ bool wallet2::parse_uri(const std::string &uri, std::string &address, std::strin
const char *ptr = strchr(remainder.c_str(), '?');
address = ptr ? remainder.substr(0, ptr-remainder.c_str()) : remainder;
- cryptonote::account_public_address addr;
- bool has_payment_id;
- crypto::hash8 new_payment_id;
- if(!get_account_integrated_address_from_str(addr, has_payment_id, new_payment_id, testnet(), address))
+ cryptonote::address_parse_info info;
+ if(!get_account_address_from_str(info, testnet(), address))
{
error = std::string("URI has wrong address: ") + address;
return false;
@@ -5841,7 +6982,7 @@ bool wallet2::parse_uri(const std::string &uri, std::string &address, std::strin
}
else if (kv[0] == "tx_payment_id")
{
- if (has_payment_id)
+ if (info.has_payment_id)
{
error = "Separate payment id given with an integrated address";
return false;
@@ -6020,8 +7161,8 @@ std::vector<std::pair<uint64_t, uint64_t>> wallet2::estimate_backlog(uint64_t mi
priority_size_max += i.blob_size;
}
- uint64_t nblocks_min = (priority_size_min + full_reward_zone - 1) / full_reward_zone;
- uint64_t nblocks_max = (priority_size_max + full_reward_zone - 1) / full_reward_zone;
+ uint64_t nblocks_min = priority_size_min / full_reward_zone;
+ uint64_t nblocks_max = priority_size_max / full_reward_zone;
MDEBUG("estimate_backlog: priority_size " << priority_size_min << " - " << priority_size_max << " for " << fee
<< " (" << our_fee_byte_min << " - " << our_fee_byte_max << " piconero byte fee), "
<< nblocks_min << " - " << nblocks_max << " blocks at block size " << full_reward_zone);