aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/CMakeLists.txt3
-rw-r--r--src/wallet/api/wallet.cpp6
-rw-r--r--src/wallet/wallet2.cpp296
-rw-r--r--src/wallet/wallet2.h31
-rw-r--r--src/wallet/wallet_args.cpp8
-rw-r--r--src/wallet/wallet_rpc_server.cpp213
-rw-r--r--src/wallet/wallet_rpc_server.h2
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h46
8 files changed, 504 insertions, 101 deletions
diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt
index 4932dd4b6..4e3fb1ae5 100644
--- a/src/wallet/CMakeLists.txt
+++ b/src/wallet/CMakeLists.txt
@@ -120,7 +120,8 @@ if (BUILD_GUI_DEPS)
ringct
ringct_basic
checkpoints
- version)
+ version
+ device_trezor)
foreach(lib ${libs_to_merge})
list(APPEND objlibs $<TARGET_OBJECTS:obj_${lib}>) # matches naming convention in src/CMakeLists.txt
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
index ddf2d74ff..7cd3b65bb 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -507,7 +507,7 @@ bool WalletImpl::createWatchOnly(const std::string &path, const std::string &pas
auto key_images = m_wallet->export_key_images();
uint64_t spent = 0;
uint64_t unspent = 0;
- view_wallet->import_key_images(key_images,spent,unspent,false);
+ view_wallet->import_key_images(key_images.second, key_images.first, spent, unspent, false);
clearStatus();
} catch (const std::exception &e) {
LOG_ERROR("Error creating view only wallet: " << e.what());
@@ -1051,8 +1051,8 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file
// Check tx data and construct confirmation message
std::string extra_message;
- if (!transaction->m_unsigned_tx_set.transfers.empty())
- extra_message = (boost::format("%u outputs to import. ") % (unsigned)transaction->m_unsigned_tx_set.transfers.size()).str();
+ if (!transaction->m_unsigned_tx_set.transfers.second.empty())
+ extra_message = (boost::format("%u outputs to import. ") % (unsigned)transaction->m_unsigned_tx_set.transfers.second.size()).str();
transaction->checkLoadedTx([&transaction](){return transaction->m_unsigned_tx_set.txes.size();}, [&transaction](size_t n)->const tools::wallet2::tx_construction_data&{return transaction->m_unsigned_tx_set.txes[n];}, extra_message);
setStatus(transaction->status(), transaction->errorString());
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index c27e4e820..129b96733 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -69,6 +69,7 @@ using namespace epee;
#include "common/base58.h"
#include "common/dns_utils.h"
#include "common/notify.h"
+#include "common/perf_timer.h"
#include "ringct/rctSigs.h"
#include "ringdb.h"
#include "device/device_cold.hpp"
@@ -112,11 +113,11 @@ using namespace cryptonote;
#define SUBADDRESS_LOOKAHEAD_MAJOR 50
#define SUBADDRESS_LOOKAHEAD_MINOR 200
-#define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\002"
+#define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\003"
#define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001"
-#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003"
+#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\004"
#define SEGREGATION_FORK_HEIGHT 99999999
#define TESTNET_SEGREGATION_FORK_HEIGHT 99999999
@@ -1605,6 +1606,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
td.m_txid = txid;
td.m_key_image = tx_scan_info[o].ki;
td.m_key_image_known = !m_watch_only && !m_multisig;
+ td.m_key_image_requested = false;
td.m_key_image_partial = m_multisig;
td.m_amount = amount;
td.m_pk_index = pk_index - 1;
@@ -1927,6 +1929,7 @@ void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::trans
entry.first->second.m_subaddr_indices = subaddr_indices;
}
+ entry.first->second.m_rings.clear();
for (const auto &in: tx.vin)
{
if (in.type() != typeid(cryptonote::txin_to_key))
@@ -2298,7 +2301,7 @@ void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashe
//----------------------------------------------------------------------------------------------------
void wallet2::update_pool_state(bool refreshed)
{
- MDEBUG("update_pool_state start");
+ MTRACE("update_pool_state start");
auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() {
if (m_encrypt_keys_after_refresh)
@@ -2317,7 +2320,7 @@ void wallet2::update_pool_state(bool refreshed)
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_transaction_pool_hashes.bin");
THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_transaction_pool_hashes.bin");
THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_tx_pool_error);
- MDEBUG("update_pool_state got pool");
+ MTRACE("update_pool_state got pool");
// remove any pending tx that's not in the pool
std::unordered_map<crypto::hash, wallet2::unconfirmed_transfer_details>::iterator it = m_unconfirmed_txs.begin();
@@ -2374,7 +2377,7 @@ void wallet2::update_pool_state(bool refreshed)
}
}
}
- MDEBUG("update_pool_state done first loop");
+ MTRACE("update_pool_state done first loop");
// remove pool txes to us that aren't in the pool anymore
// but only if we just refreshed, so that the tx can go in
@@ -2383,7 +2386,7 @@ void wallet2::update_pool_state(bool refreshed)
if (refreshed)
remove_obsolete_pool_txs(res.tx_hashes);
- MDEBUG("update_pool_state done second loop");
+ MTRACE("update_pool_state done second loop");
// gather txids of new pool txes to us
std::vector<std::pair<crypto::hash, bool>> txids;
@@ -2520,7 +2523,7 @@ void wallet2::update_pool_state(bool refreshed)
LOG_PRINT_L0("Error calling gettransactions daemon RPC: r " << r << ", status " << res.status);
}
}
- MDEBUG("update_pool_state end");
+ MTRACE("update_pool_state end");
}
//----------------------------------------------------------------------------------------------------
void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force)
@@ -2728,7 +2731,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
short_chain_history.clear();
get_short_chain_history(short_chain_history);
fast_refresh(stop_height, blocks_start_height, short_chain_history, true);
- THROW_WALLET_EXCEPTION_IF(m_blockchain.size() != stop_height, error::wallet_internal_error, "Unexpected hashchain size");
+ THROW_WALLET_EXCEPTION_IF((m_blockchain.size() == stop_height || (m_blockchain.size() == 1 && stop_height == 0) ? false : true), error::wallet_internal_error, "Unexpected hashchain size");
THROW_WALLET_EXCEPTION_IF(m_blockchain.offset() != 0, error::wallet_internal_error, "Unexpected hashchain offset");
for (const auto &h: tip)
m_blockchain.push_back(h);
@@ -3149,13 +3152,22 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
crypto::chacha20(account_data.data(), account_data.size(), key, keys_file_data.iv, &cipher[0]);
keys_file_data.account_data = cipher;
- unlock_keys_file();
+ std::string tmp_file_name = keys_file_name + ".new";
std::string buf;
r = ::serialization::dump_binary(keys_file_data, buf);
- r = r && epee::file_io_utils::save_string_to_file(keys_file_name, buf); //and never touch wallet_keys_file again, only read
- CHECK_AND_ASSERT_MES(r, false, "failed to generate wallet keys file " << keys_file_name);
+ r = r && epee::file_io_utils::save_string_to_file(tmp_file_name, buf);
+ CHECK_AND_ASSERT_MES(r, false, "failed to generate wallet keys file " << tmp_file_name);
+
+ unlock_keys_file();
+ std::error_code e = tools::replace_file(tmp_file_name, keys_file_name);
lock_keys_file();
+ if (e) {
+ boost::filesystem::remove(tmp_file_name);
+ LOG_ERROR("failed to update wallet keys file " << keys_file_name);
+ return false;
+ }
+
return true;
}
//----------------------------------------------------------------------------------------------------
@@ -5094,11 +5106,27 @@ void wallet2::rescan_spent()
}
}
//----------------------------------------------------------------------------------------------------
-void wallet2::rescan_blockchain(bool refresh)
+void wallet2::rescan_blockchain(bool hard, bool refresh)
{
- clear();
+ if(hard)
+ {
+ clear();
+ setup_new_blockchain();
+ }
+ else
+ {
+ m_blockchain.clear();
+ m_transfers.clear();
+ m_key_images.clear();
+ m_pub_keys.clear();
+ m_scanned_pool_txs[0].clear();
+ m_scanned_pool_txs[1].clear();
- setup_new_blockchain();
+ cryptonote::block b;
+ generate_genesis(b);
+ m_blockchain.push_back(get_block_hash(b));
+ m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx);
+ }
if (refresh)
this->refresh(false);
@@ -5465,7 +5493,7 @@ std::string wallet2::dump_tx_to_str(const std::vector<pending_tx> &ptx_vector) c
txs.txes.push_back(get_construction_data_with_decrypted_short_payment_id(tx, m_account.get_device()));
}
- txs.transfers = m_transfers;
+ txs.transfers = export_outputs();
// save as binary
std::ostringstream oss;
boost::archive::portable_binary_oarchive ar(oss);
@@ -6000,10 +6028,7 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto
rct::RangeProofType range_proof_type = rct::RangeProofBorromean;
if (sd.use_bulletproofs)
{
- range_proof_type = rct::RangeProofBulletproof;
- for (const rct::Bulletproof &proof: ptx.tx.rct_signatures.p.bulletproofs)
- if (proof.V.size() > 1)
- range_proof_type = rct::RangeProofPaddedBulletproof;
+ range_proof_type = rct::RangeProofPaddedBulletproof;
}
bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts.addr, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, sd.use_rct, range_proof_type, &msout, false);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype);
@@ -6814,21 +6839,23 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
//static const double shape = m_testnet ? 17.02 : 17.28;
static const double scale = 1/1.61;
std::gamma_distribution<double> gamma(shape, scale);
+ THROW_WALLET_EXCEPTION_IF(rct_offsets.size() <= CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, error::wallet_internal_error, "Bad offset calculation");
+ uint64_t last_usable_block = rct_offsets.size() - 1;
auto pick_gamma = [&]()
{
double x = gamma(engine);
x = exp(x);
uint64_t block_offset = x / DIFFICULTY_TARGET_V2; // this assumes constant target over the whole rct range
- if (block_offset >= rct_offsets.size() - 1)
+ if (block_offset > last_usable_block - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE)
return std::numeric_limits<uint64_t>::max(); // bad pick
- block_offset = rct_offsets.size() - 2 - block_offset;
- THROW_WALLET_EXCEPTION_IF(block_offset >= rct_offsets.size() - 1, error::wallet_internal_error, "Bad offset calculation");
- THROW_WALLET_EXCEPTION_IF(rct_offsets[block_offset + 1] < rct_offsets[block_offset],
+ block_offset = last_usable_block - block_offset;
+ THROW_WALLET_EXCEPTION_IF(block_offset > last_usable_block, error::wallet_internal_error, "Bad offset calculation");
+ THROW_WALLET_EXCEPTION_IF(block_offset > 0 && rct_offsets[block_offset] < rct_offsets[block_offset - 1],
error::get_output_distribution, "Decreasing offsets in rct distribution: " +
- std::to_string(block_offset) + ": " + std::to_string(rct_offsets[block_offset]) + ", " +
- std::to_string(block_offset + 1) + ": " + std::to_string(rct_offsets[block_offset + 1]));
+ std::to_string(block_offset - 1) + ": " + std::to_string(rct_offsets[block_offset - 1]) + ", " +
+ std::to_string(block_offset) + ": " + std::to_string(rct_offsets[block_offset]));
uint64_t first_block_offset = block_offset, last_block_offset = block_offset;
- for (size_t half_window = 0; half_window < GAMMA_PICK_HALF_WINDOW; ++half_window)
+ for (size_t half_window = 0; half_window <= GAMMA_PICK_HALF_WINDOW; ++half_window)
{
// end when we have a non empty block
uint64_t cum0 = first_block_offset > 0 ? rct_offsets[first_block_offset] - rct_offsets[first_block_offset - 1] : rct_offsets[0];
@@ -6837,19 +6864,24 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
uint64_t cum1 = last_block_offset > 0 ? rct_offsets[last_block_offset] - rct_offsets[last_block_offset - 1] : rct_offsets[0];
if (cum1 > 1)
break;
- if (first_block_offset == 0 && last_block_offset >= rct_offsets.size() - 2)
+ if (first_block_offset == 0 && last_block_offset >= last_usable_block)
break;
// expand up to bounds
if (first_block_offset > 0)
--first_block_offset;
- if (last_block_offset < rct_offsets.size() - 1)
+ else
+ return std::numeric_limits<uint64_t>::max(); // bad pick
+ if (last_block_offset < last_usable_block - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE)
++last_block_offset;
+ else
+ return std::numeric_limits<uint64_t>::max(); // bad pick
}
- const uint64_t n_rct = rct_offsets[last_block_offset] - (first_block_offset == 0 ? 0 : rct_offsets[first_block_offset - 1]);
+ const uint64_t first_rct = first_block_offset == 0 ? 0 : rct_offsets[first_block_offset - 1];
+ const uint64_t n_rct = rct_offsets[last_block_offset] - first_rct;
if (n_rct == 0)
return rct_offsets[block_offset] ? rct_offsets[block_offset] - 1 : 0;
- MDEBUG("Picking 1/" << n_rct << " in " << (last_block_offset - first_block_offset + 1) << " blocks centered around " << block_offset);
- return rct_offsets[first_block_offset] + crypto::rand<uint64_t>() % n_rct;
+ MDEBUG("Picking 1/" << n_rct << " in " << (last_block_offset - first_block_offset + 1) << " blocks centered around " << block_offset + rct_start_height);
+ return first_rct + crypto::rand<uint64_t>() % n_rct;
};
size_t num_selected_transfers = 0;
@@ -7188,6 +7220,9 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
break;
}
}
+ bool use_histogram = amount != 0 || !has_rct_distribution;
+ if (!use_histogram)
+ num_outs = rct_offsets[rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE];
// make sure the real outputs we asked for are really included, along
// with the correct key and mask: this guards against an active attack
@@ -7280,6 +7315,20 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
outs.push_back(v);
}
}
+
+ // save those outs in the ringdb for reuse
+ for (size_t i = 0; i < selected_transfers.size(); ++i)
+ {
+ const size_t idx = selected_transfers[i];
+ THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error, "selected_transfers entry out of range");
+ const transfer_details &td = m_transfers[idx];
+ std::vector<uint64_t> ring;
+ ring.reserve(outs[i].size());
+ for (const auto &e: outs[i])
+ ring.push_back(std::get<0>(e));
+ if (!set_ring(td.m_key_image, ring, false))
+ MERROR("Failed to set ring for " << td.m_key_image);
+ }
}
template<typename T>
@@ -7951,6 +8000,7 @@ void wallet2::light_wallet_get_unspent_outs()
td.m_key_image = unspent_key_image;
td.m_key_image_known = !m_watch_only && !m_multisig;
+ td.m_key_image_requested = false;
td.m_key_image_partial = m_multisig;
td.m_amount = o.amount;
td.m_pk_index = 0;
@@ -9124,7 +9174,7 @@ void wallet2::cold_sign_tx(const std::vector<pending_tx>& ptx_vector, signed_tx_
{
txs.txes.push_back(get_construction_data_with_decrypted_short_payment_id(tx, m_account.get_device()));
}
- txs.transfers = m_transfers;
+ txs.transfers = std::make_pair(0, m_transfers);
auto dev_cold = dynamic_cast<::hw::device_cold*>(&hwdev);
CHECK_AND_ASSERT_THROW_MES(dev_cold, "Device does not implement cold signing interface");
@@ -9155,7 +9205,7 @@ uint64_t wallet2::cold_key_image_sync(uint64_t &spent, uint64_t &unspent) {
dev_cold->ki_sync(&wallet_shim, m_transfers, ski);
- return import_key_images(ski, spent, unspent);
+ return import_key_images(ski, 0, spent, unspent);
}
//----------------------------------------------------------------------------------------------------
void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height) const
@@ -10517,31 +10567,45 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle
bool wallet2::export_key_images(const std::string &filename) const
{
- std::vector<std::pair<crypto::key_image, crypto::signature>> ski = export_key_images();
+ PERF_TIMER(export_key_images);
+ std::pair<size_t, 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));
const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
+ const uint32_t offset = ski.first;
std::string data;
+ data.reserve(4 + ski.second.size() * (sizeof(crypto::key_image) + sizeof(crypto::signature)) + 2 * sizeof(crypto::public_key));
+ data.resize(4);
+ data[0] = offset & 0xff;
+ data[1] = (offset >> 8) & 0xff;
+ data[2] = (offset >> 16) & 0xff;
+ data[3] = (offset >> 24) & 0xff;
data += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key));
data += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key));
- for (const auto &i: ski)
+ for (const auto &i: ski.second)
{
data += std::string((const char *)&i.first, sizeof(crypto::key_image));
data += std::string((const char *)&i.second, sizeof(crypto::signature));
}
// encrypt data, keep magic plaintext
+ PERF_TIMER(export_key_images_encrypt);
std::string ciphertext = encrypt_with_view_secret_key(data);
return epee::file_io_utils::save_string_to_file(filename, magic + ciphertext);
}
//----------------------------------------------------------------------------------------------------
-std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key_images() const
+std::pair<size_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> wallet2::export_key_images() const
{
+ PERF_TIMER(export_key_images_raw);
std::vector<std::pair<crypto::key_image, crypto::signature>> ski;
- ski.reserve(m_transfers.size());
- for (size_t n = 0; n < m_transfers.size(); ++n)
+ size_t offset = 0;
+ while (offset < m_transfers.size() && !m_transfers[offset].m_key_image_requested)
+ ++offset;
+
+ ski.reserve(m_transfers.size() - offset);
+ for (size_t n = offset; n < m_transfers.size(); ++n)
{
const transfer_details &td = m_transfers[n];
@@ -10585,11 +10649,12 @@ std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key
ski.push_back(std::make_pair(td.m_key_image, signature));
}
- return ski;
+ return std::make_pair(offset, ski);
}
uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent)
{
+ PERF_TIMER(import_key_images_fsu);
std::string data;
bool r = epee::file_io_utils::load_file_to_string(filename, data);
@@ -10603,6 +10668,7 @@ uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent
try
{
+ PERF_TIMER(import_key_images_decrypt);
data = decrypt_with_view_secret_key(std::string(data, magiclen));
}
catch (const std::exception &e)
@@ -10610,15 +10676,17 @@ uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent
THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to decrypt ") + filename + ": " + e.what());
}
- const size_t headerlen = 2 * sizeof(crypto::public_key);
+ const size_t headerlen = 4 + 2 * sizeof(crypto::public_key);
THROW_WALLET_EXCEPTION_IF(data.size() < headerlen, error::wallet_internal_error, std::string("Bad data size from file ") + filename);
- const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0];
- const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)];
+ const uint32_t offset = (uint8_t)data[0] | (((uint8_t)data[1]) << 8) | (((uint8_t)data[2]) << 16) | (((uint8_t)data[3]) << 24);
+ const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[4];
+ const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[4 + sizeof(crypto::public_key)];
const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key)
{
THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string( "Key images from ") + filename + " are for a different account");
}
+ THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Offset larger than known outputs");
const size_t record_size = sizeof(crypto::key_image) + sizeof(crypto::signature);
THROW_WALLET_EXCEPTION_IF((data.size() - headerlen) % record_size,
@@ -10635,28 +10703,33 @@ uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent
ski.push_back(std::make_pair(key_image, signature));
}
- return import_key_images(ski, spent, unspent);
+ return import_key_images(ski, offset, spent, unspent);
}
//----------------------------------------------------------------------------------------------------
-uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, uint64_t &spent, uint64_t &unspent, bool check_spent)
+uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, size_t offset, uint64_t &spent, uint64_t &unspent, bool check_spent)
{
+ PERF_TIMER(import_key_images_lots);
COMMAND_RPC_IS_KEY_IMAGE_SPENT::request req = AUTO_VAL_INIT(req);
COMMAND_RPC_IS_KEY_IMAGE_SPENT::response daemon_resp = AUTO_VAL_INIT(daemon_resp);
- THROW_WALLET_EXCEPTION_IF(signed_key_images.size() > m_transfers.size(), error::wallet_internal_error,
+ THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Offset larger than known outputs");
+ THROW_WALLET_EXCEPTION_IF(signed_key_images.size() > m_transfers.size() - offset, error::wallet_internal_error,
"The blockchain is out of date compared to the signed key images");
- if (signed_key_images.empty())
+ if (signed_key_images.empty() && offset == 0)
{
spent = 0;
unspent = 0;
return 0;
}
+ req.key_images.reserve(signed_key_images.size());
+
+ PERF_TIMER_START(import_key_images_A);
for (size_t n = 0; n < signed_key_images.size(); ++n)
{
- const transfer_details &td = m_transfers[n];
+ const transfer_details &td = m_transfers[n + offset];
const crypto::key_image &key_image = signed_key_images[n].first;
const crypto::signature &signature = signed_key_images[n].second;
@@ -10667,30 +10740,37 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
const cryptonote::txout_to_key &o = boost::get<cryptonote::txout_to_key>(out.target);
const crypto::public_key pkey = o.key;
- std::vector<const crypto::public_key*> pkeys;
- pkeys.push_back(&pkey);
- THROW_WALLET_EXCEPTION_IF(!(rct::scalarmultKey(rct::ki2rct(key_image), rct::curveOrder()) == rct::identity()),
- error::wallet_internal_error, "Key image out of validity domain: input " + boost::lexical_cast<std::string>(n) + "/"
- + boost::lexical_cast<std::string>(signed_key_images.size()) + ", key image " + epee::string_tools::pod_to_hex(key_image));
-
- THROW_WALLET_EXCEPTION_IF(!crypto::check_ring_signature((const crypto::hash&)key_image, key_image, pkeys, &signature),
- error::signature_check_failed, boost::lexical_cast<std::string>(n) + "/"
- + boost::lexical_cast<std::string>(signed_key_images.size()) + ", key image " + epee::string_tools::pod_to_hex(key_image)
- + ", signature " + epee::string_tools::pod_to_hex(signature) + ", pubkey " + epee::string_tools::pod_to_hex(*pkeys[0]));
+ if (!td.m_key_image_known || !(key_image == td.m_key_image))
+ {
+ std::vector<const crypto::public_key*> pkeys;
+ pkeys.push_back(&pkey);
+ THROW_WALLET_EXCEPTION_IF(!(rct::scalarmultKey(rct::ki2rct(key_image), rct::curveOrder()) == rct::identity()),
+ error::wallet_internal_error, "Key image out of validity domain: input " + boost::lexical_cast<std::string>(n + offset) + "/"
+ + boost::lexical_cast<std::string>(signed_key_images.size()) + ", key image " + epee::string_tools::pod_to_hex(key_image));
+ THROW_WALLET_EXCEPTION_IF(!crypto::check_ring_signature((const crypto::hash&)key_image, key_image, pkeys, &signature),
+ error::signature_check_failed, boost::lexical_cast<std::string>(n + offset) + "/"
+ + boost::lexical_cast<std::string>(signed_key_images.size()) + ", key image " + epee::string_tools::pod_to_hex(key_image)
+ + ", signature " + epee::string_tools::pod_to_hex(signature) + ", pubkey " + epee::string_tools::pod_to_hex(*pkeys[0]));
+ }
req.key_images.push_back(epee::string_tools::pod_to_hex(key_image));
}
+ PERF_TIMER_STOP(import_key_images_A);
+ PERF_TIMER_START(import_key_images_B);
for (size_t n = 0; n < signed_key_images.size(); ++n)
{
- m_transfers[n].m_key_image = signed_key_images[n].first;
- m_key_images[m_transfers[n].m_key_image] = n;
- m_transfers[n].m_key_image_known = true;
- m_transfers[n].m_key_image_partial = false;
+ m_transfers[n + offset].m_key_image = signed_key_images[n].first;
+ m_key_images[m_transfers[n + offset].m_key_image] = n + offset;
+ m_transfers[n + offset].m_key_image_known = true;
+ m_transfers[n + offset].m_key_image_requested = false;
+ m_transfers[n + offset].m_key_image_partial = false;
}
+ PERF_TIMER_STOP(import_key_images_B);
if(check_spent)
{
+ PERF_TIMER(import_key_images_RPC);
m_daemon_rpc_mutex.lock();
bool r = epee::net_utils::invoke_http_json("/is_key_image_spent", req, daemon_resp, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
@@ -10702,7 +10782,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(signed_key_images.size()));
for (size_t n = 0; n < daemon_resp.spent_status.size(); ++n)
{
- transfer_details &td = m_transfers[n];
+ transfer_details &td = m_transfers[n + offset];
td.m_spent = daemon_resp.spent_status[n] != COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT;
}
}
@@ -10713,6 +10793,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
// was created by sweep_all, so we can't know the spent height and other detailed info.
std::unordered_map<crypto::key_image, crypto::hash> spent_key_images;
+ PERF_TIMER_START(import_key_images_C);
for (const transfer_details &td: m_transfers)
{
for (const cryptonote::txin_v& in : td.m_tx.vin)
@@ -10721,10 +10802,12 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
spent_key_images.insert(std::make_pair(boost::get<cryptonote::txin_to_key>(in).k_image, td.m_txid));
}
}
+ PERF_TIMER_STOP(import_key_images_C);
+ PERF_TIMER_START(import_key_images_D);
for(size_t i = 0; i < signed_key_images.size(); ++i)
{
- transfer_details &td = m_transfers[i];
+ const transfer_details &td = m_transfers[i + offset];
uint64_t amount = td.amount();
if (td.m_spent)
spent += amount;
@@ -10742,6 +10825,8 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
spent_txids.insert(skii->second);
}
}
+ PERF_TIMER_STOP(import_key_images_D);
+
MDEBUG("Total: " << print_money(spent) << " spent, " << print_money(unspent) << " unspent");
if (check_spent)
@@ -10751,8 +10836,12 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
COMMAND_RPC_GET_TRANSACTIONS::response gettxs_res;
gettxs_req.decode_as_json = false;
gettxs_req.prune = false;
+ gettxs_req.txs_hashes.reserve(spent_txids.size());
for (const crypto::hash& spent_txid : spent_txids)
gettxs_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(spent_txid));
+
+
+ PERF_TIMER_START(import_key_images_E);
m_daemon_rpc_mutex.lock();
bool r = epee::net_utils::invoke_http_json("/gettransactions", gettxs_req, gettxs_res, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
@@ -10760,8 +10849,10 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
THROW_WALLET_EXCEPTION_IF(gettxs_res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions");
THROW_WALLET_EXCEPTION_IF(gettxs_res.txs.size() != spent_txids.size(), error::wallet_internal_error,
"daemon returned wrong response for gettransactions, wrong count = " + std::to_string(gettxs_res.txs.size()) + ", expected " + std::to_string(spent_txids.size()));
+ PERF_TIMER_STOP(import_key_images_E);
// process each outgoing tx
+ PERF_TIMER_START(import_key_images_F);
auto spent_txid = spent_txids.begin();
hw::device &hwdev = m_account.get_device();
for (const COMMAND_RPC_GET_TRANSACTIONS::entry& e : gettxs_res.txs)
@@ -10857,7 +10948,9 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
++spent_txid;
}
+ PERF_TIMER_STOP(import_key_images_F);
+ PERF_TIMER_START(import_key_images_G);
for (size_t n : swept_transfers)
{
const transfer_details& td = m_transfers[n];
@@ -10868,6 +10961,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
const crypto::hash &spent_txid = crypto::null_hash; // spent txid is unknown
m_confirmed_txs.insert(std::make_pair(spent_txid, pd));
}
+ PERF_TIMER_STOP(import_key_images_G);
}
return m_transfers[signed_key_images.size() - 1].m_block_height;
@@ -10888,6 +10982,7 @@ bool wallet2::import_key_images(std::vector<crypto::key_image> key_images)
td.m_key_image = key_images[i];
m_key_images[m_transfers[i].m_key_image] = i;
td.m_key_image_known = true;
+ td.m_key_image_requested = false;
td.m_key_image_partial = false;
m_pub_keys[m_transfers[i].get_public_key()] = i;
}
@@ -10953,50 +11048,87 @@ void wallet2::import_blockchain(const std::tuple<size_t, crypto::hash, std::vect
m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx);
}
//----------------------------------------------------------------------------------------------------
-std::vector<tools::wallet2::transfer_details> wallet2::export_outputs() const
+std::pair<size_t, std::vector<tools::wallet2::transfer_details>> wallet2::export_outputs() const
{
+ PERF_TIMER(export_outputs);
std::vector<tools::wallet2::transfer_details> outs;
- outs.reserve(m_transfers.size());
- for (size_t n = 0; n < m_transfers.size(); ++n)
+ size_t offset = 0;
+ while (offset < m_transfers.size() && m_transfers[offset].m_key_image_known)
+ ++offset;
+
+ outs.reserve(m_transfers.size() - offset);
+ for (size_t n = offset; n < m_transfers.size(); ++n)
{
const transfer_details &td = m_transfers[n];
outs.push_back(td);
}
- return outs;
+ return std::make_pair(offset, outs);
}
//----------------------------------------------------------------------------------------------------
std::string wallet2::export_outputs_to_str() const
{
- std::vector<tools::wallet2::transfer_details> outs = export_outputs();
+ PERF_TIMER(export_outputs_to_str);
std::stringstream oss;
boost::archive::portable_binary_oarchive ar(oss);
- ar << outs;
+ const auto& outputs = export_outputs();
+ ar << outputs;
std::string magic(OUTPUT_EXPORT_FILE_MAGIC, strlen(OUTPUT_EXPORT_FILE_MAGIC));
const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
std::string header;
header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key));
header += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key));
+ PERF_TIMER(export_outputs_encryption);
std::string ciphertext = encrypt_with_view_secret_key(header + oss.str());
return magic + ciphertext;
}
//----------------------------------------------------------------------------------------------------
-size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_details> &outputs)
+size_t wallet2::import_outputs(const std::pair<size_t, std::vector<tools::wallet2::transfer_details>> &outputs)
{
- m_transfers.clear();
- m_transfers.reserve(outputs.size());
- for (size_t i = 0; i < outputs.size(); ++i)
+ PERF_TIMER(import_outputs);
+
+ THROW_WALLET_EXCEPTION_IF(outputs.first > m_transfers.size(), error::wallet_internal_error,
+ "Imported outputs omit more outputs that we know of");
+
+ const size_t offset = outputs.first;
+ const size_t original_size = m_transfers.size();
+ m_transfers.resize(offset + outputs.second.size());
+ for (size_t i = 0; i < offset; ++i)
+ m_transfers[i].m_key_image_requested = false;
+ for (size_t i = 0; i < outputs.second.size(); ++i)
{
- transfer_details td = outputs[i];
+ transfer_details td = outputs.second[i];
+
+ // skip those we've already imported, or which have different data
+ if (i + offset < original_size)
+ {
+ // compare the data used to create the key image below
+ const transfer_details &org_td = m_transfers[i + offset];
+ if (!org_td.m_key_image_known)
+ goto process;
+#define CMPF(f) if (!(td.f == org_td.f)) goto process
+ CMPF(m_txid);
+ CMPF(m_key_image);
+ CMPF(m_internal_output_index);
+#undef CMPF
+ if (!(get_transaction_prefix_hash(td.m_tx) == get_transaction_prefix_hash(org_td.m_tx)))
+ goto process;
+
+ // copy anyway, since the comparison does not include ancillary fields which may have changed
+ m_transfers[i + offset] = std::move(td);
+ continue;
+ }
+
+process:
// the hot wallet wouldn't have known about key images (except if we already exported them)
cryptonote::keypair in_ephemeral;
- 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(td.m_tx.vout.empty(), error::wallet_internal_error, "tx with no outputs at index " + boost::lexical_cast<std::string>(i + offset));
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);
@@ -11007,13 +11139,14 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail
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;
+ td.m_key_image_requested = true;
td.m_key_image_partial = false;
THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != out_key,
- error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + boost::lexical_cast<std::string>(i));
+ error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + boost::lexical_cast<std::string>(i + offset));
- m_key_images[td.m_key_image] = m_transfers.size();
- m_pub_keys[td.get_public_key()] = m_transfers.size();
- m_transfers.push_back(std::move(td));
+ m_key_images[td.m_key_image] = i + offset;
+ m_pub_keys[td.get_public_key()] = i + offset;
+ m_transfers[i + offset] = std::move(td);
}
return m_transfers.size();
@@ -11021,6 +11154,7 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail
//----------------------------------------------------------------------------------------------------
size_t wallet2::import_outputs_from_str(const std::string &outputs_st)
{
+ PERF_TIMER(import_outputs_from_str);
std::string data = outputs_st;
const size_t magiclen = strlen(OUTPUT_EXPORT_FILE_MAGIC);
if (data.size() < magiclen || memcmp(data.data(), OUTPUT_EXPORT_FILE_MAGIC, magiclen))
@@ -11030,6 +11164,7 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st)
try
{
+ PERF_TIMER(import_outputs_decrypt);
data = decrypt_with_view_secret_key(std::string(data, magiclen));
}
catch (const std::exception &e)
@@ -11056,7 +11191,7 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st)
std::string body(data, headerlen);
std::stringstream iss;
iss << body;
- std::vector<tools::wallet2::transfer_details> outputs;
+ std::pair<size_t, std::vector<tools::wallet2::transfer_details>> outputs;
try
{
boost::archive::portable_binary_iarchive ar(iss);
@@ -11248,6 +11383,7 @@ void wallet2::update_multisig_rescan_info(const std::vector<std::vector<rct::key
m_key_images.erase(td.m_key_image);
td.m_key_image = get_multisig_composite_key_image(n);
td.m_key_image_known = true;
+ td.m_key_image_requested = false;
td.m_key_image_partial = false;
td.m_multisig_k = multisig_k[n];
m_key_images[td.m_key_image] = n;
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 7d78a3fee..eb0763131 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -230,7 +230,7 @@ namespace tools
bool error;
boost::optional<cryptonote::subaddress_receive_info> received;
- tx_scan_info_t(): money_transfered(0), error(true) {}
+ tx_scan_info_t(): amount(0), money_transfered(0), error(true) {}
};
struct transfer_details
@@ -247,6 +247,7 @@ namespace tools
uint64_t m_amount;
bool m_rct;
bool m_key_image_known;
+ bool m_key_image_requested;
size_t m_pk_index;
cryptonote::subaddress_index m_subaddr_index;
bool m_key_image_partial;
@@ -270,6 +271,7 @@ namespace tools
FIELD(m_amount)
FIELD(m_rct)
FIELD(m_key_image_known)
+ FIELD(m_key_image_requested)
FIELD(m_pk_index)
FIELD(m_subaddr_index)
FIELD(m_key_image_partial)
@@ -417,7 +419,7 @@ namespace tools
struct unsigned_tx_set
{
std::vector<tx_construction_data> txes;
- wallet2::transfer_container transfers;
+ std::pair<size_t, wallet2::transfer_container> transfers;
};
struct signed_tx_set
@@ -786,7 +788,7 @@ namespace tools
uint64_t get_blockchain_current_height() const { return m_light_wallet_blockchain_height ? m_light_wallet_blockchain_height : m_blockchain.size(); }
void rescan_spent();
- void rescan_blockchain(bool refresh = true);
+ void rescan_blockchain(bool hard, bool refresh = true);
bool is_transfer_unlocked(const transfer_details& td) const;
bool is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height) const;
@@ -1071,9 +1073,9 @@ namespace tools
bool verify_with_public_key(const std::string &data, const crypto::public_key &public_key, const std::string &signature) const;
// Import/Export wallet data
- std::vector<tools::wallet2::transfer_details> export_outputs() const;
+ std::pair<size_t, std::vector<tools::wallet2::transfer_details>> export_outputs() const;
std::string export_outputs_to_str() const;
- size_t import_outputs(const std::vector<tools::wallet2::transfer_details> &outputs);
+ size_t import_outputs(const std::pair<size_t, std::vector<tools::wallet2::transfer_details>> &outputs);
size_t import_outputs_from_str(const std::string &outputs_st);
payment_container export_payments() const;
void import_payments(const payment_container &payments);
@@ -1081,8 +1083,8 @@ namespace tools
std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> export_blockchain() const;
void import_blockchain(const std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> &bc);
bool export_key_images(const std::string &filename) const;
- std::vector<std::pair<crypto::key_image, crypto::signature>> export_key_images() const;
- uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, uint64_t &spent, uint64_t &unspent, bool check_spent = true);
+ std::pair<size_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> export_key_images() const;
+ uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, size_t offset, uint64_t &spent, uint64_t &unspent, bool check_spent = true);
uint64_t import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent);
bool import_key_images(std::vector<crypto::key_image> key_images);
crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const;
@@ -1199,6 +1201,8 @@ namespace tools
void set_tx_notify(const std::shared_ptr<tools::Notify> &notify) { m_tx_notify = notify; }
+ bool is_tx_spendtime_unlocked(uint64_t unlock_time, uint64_t block_height) const;
+
private:
/*!
* \brief Stores wallet information to wallet file.
@@ -1218,7 +1222,6 @@ namespace tools
void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector<tx_cache_data> &tx_cache_data, size_t tx_cache_data_offset);
void detach_blockchain(uint64_t height);
void get_short_chain_history(std::list<crypto::hash>& ids, uint64_t granularity = 1) const;
- bool is_tx_spendtime_unlocked(uint64_t unlock_time, uint64_t block_height) const;
bool clear();
void pull_blocks(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices);
void pull_hashes(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<crypto::hash> &hashes);
@@ -1396,7 +1399,7 @@ namespace tools
};
}
BOOST_CLASS_VERSION(tools::wallet2, 26)
-BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 9)
+BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 10)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0)
BOOST_CLASS_VERSION(tools::wallet2::multisig_tx_set, 1)
@@ -1454,6 +1457,10 @@ namespace boost
x.m_multisig_k.clear();
x.m_multisig_info.clear();
}
+ if (ver < 10)
+ {
+ x.m_key_image_requested = false;
+ }
}
template <class Archive>
@@ -1535,6 +1542,12 @@ namespace boost
a & x.m_multisig_info;
a & x.m_multisig_k;
a & x.m_key_image_partial;
+ if (ver < 10)
+ {
+ initialize_transfer_details(a, x, ver);
+ return;
+ }
+ a & x.m_key_image_requested;
}
template <class Archive>
diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp
index 95a4e0ad6..b9d0a6a75 100644
--- a/src/wallet/wallet_args.cpp
+++ b/src/wallet/wallet_args.cpp
@@ -211,6 +211,14 @@ namespace wallet_args
Print(print) << boost::format(wallet_args::tr("Logging to %s")) % log_path;
+ const ssize_t lockable_memory = tools::get_lockable_memory();
+ if (lockable_memory >= 0 && lockable_memory < 256 * 4096) // 256 pages -> at least 256 secret keys and other such small/medium objects
+ Print(print) << tr("WARNING: You may not have a high enough lockable memory limit")
+#ifdef ELPP_OS_UNIX
+ << ", " << tr("see ulimit -l")
+#endif
+ ;
+
return {std::move(vm), should_terminate};
}
}
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 5e6100dfd..533784acf 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -982,7 +982,11 @@ namespace tools
{
res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
if (req.get_tx_keys)
+ {
res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key));
+ for (const crypto::secret_key& additional_tx_key : ptx.additional_tx_keys)
+ res.tx_key_list.back() += epee::string_tools::pod_to_hex(additional_tx_key);
+ }
}
if (req.export_raw)
@@ -1781,7 +1785,7 @@ namespace tools
try
{
- m_wallet->rescan_blockchain();
+ m_wallet->rescan_blockchain(req.hard);
}
catch (const std::exception& e)
{
@@ -2460,12 +2464,13 @@ namespace tools
if (!m_wallet) return not_open(er);
try
{
- std::vector<std::pair<crypto::key_image, crypto::signature>> ski = m_wallet->export_key_images();
- res.signed_key_images.resize(ski.size());
- for (size_t n = 0; n < ski.size(); ++n)
+ std::pair<size_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> ski = m_wallet->export_key_images();
+ res.offset = ski.first;
+ res.signed_key_images.resize(ski.second.size());
+ for (size_t n = 0; n < ski.second.size(); ++n)
{
- res.signed_key_images[n].key_image = epee::string_tools::pod_to_hex(ski[n].first);
- res.signed_key_images[n].signature = epee::string_tools::pod_to_hex(ski[n].second);
+ res.signed_key_images[n].key_image = epee::string_tools::pod_to_hex(ski.second[n].first);
+ res.signed_key_images[n].signature = epee::string_tools::pod_to_hex(ski.second[n].second);
}
}
@@ -2518,7 +2523,7 @@ namespace tools
ski[n].second = *reinterpret_cast<const crypto::signature*>(bd.data());
}
uint64_t spent = 0, unspent = 0;
- uint64_t height = m_wallet->import_key_images(ski, spent, unspent);
+ uint64_t height = m_wallet->import_key_images(ski, req.offset, spent, unspent);
res.spent = spent;
res.unspent = unspent;
res.height = height;
@@ -3090,6 +3095,200 @@ namespace tools
}
}
//------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_restore_deterministic_wallet(const wallet_rpc::COMMAND_RPC_RESTORE_DETERMINISTIC_WALLET::request &req, wallet_rpc::COMMAND_RPC_RESTORE_DETERMINISTIC_WALLET::response &res, epee::json_rpc::error &er)
+ {
+ if (m_wallet_dir.empty())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_NO_WALLET_DIR;
+ er.message = "No wallet dir configured";
+ return false;
+ }
+
+ // early check for mandatory fields
+ if (req.filename.empty())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "field 'filename' is mandatory. Please provide a filename to save the restored wallet to.";
+ return false;
+ }
+ if (req.seed.empty())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "field 'seed' is mandatory. Please provide a seed you want to restore from.";
+ return false;
+ }
+
+ namespace po = boost::program_options;
+ po::variables_map vm2;
+ const char *ptr = strchr(req.filename.c_str(), '/');
+ #ifdef _WIN32
+ if (!ptr)
+ ptr = strchr(req.filename.c_str(), '\\');
+ if (!ptr)
+ ptr = strchr(req.filename.c_str(), ':');
+ #endif
+ if (ptr)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Invalid filename";
+ return false;
+ }
+ std::string wallet_file = m_wallet_dir + "/" + req.filename;
+ // check if wallet file already exists
+ if (!wallet_file.empty())
+ {
+ try
+ {
+ boost::system::error_code ignored_ec;
+ THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(wallet_file, ignored_ec), error::file_exists, wallet_file);
+ }
+ catch (const std::exception &e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Wallet already exists.";
+ return false;
+ }
+ }
+ crypto::secret_key recovery_key;
+ std::string old_language;
+
+ // check the given seed
+ {
+ if (!crypto::ElectrumWords::words_to_bytes(req.seed, recovery_key, old_language))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Electrum-style word list failed verification";
+ return false;
+ }
+ }
+
+ // process seed_offset if given
+ {
+ if (!req.seed_offset.empty())
+ {
+ recovery_key = cryptonote::decrypt_key(recovery_key, req.seed_offset);
+ }
+ }
+ {
+ po::options_description desc("dummy");
+ const command_line::arg_descriptor<std::string, true> arg_password = {"password", "password"};
+ const char *argv[4];
+ int argc = 3;
+ argv[0] = "wallet-rpc";
+ argv[1] = "--password";
+ argv[2] = req.password.c_str();
+ argv[3] = NULL;
+ vm2 = *m_vm;
+ command_line::add_arg(desc, arg_password);
+ po::store(po::parse_command_line(argc, argv, desc), vm2);
+ }
+
+ auto rc = tools::wallet2::make_new(vm2, true, nullptr);
+ std::unique_ptr<wallet2> wal;
+ wal = std::move(rc.first);
+ if (!wal)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to create wallet";
+ return false;
+ }
+
+ epee::wipeable_string password = rc.second.password();
+
+ bool was_deprecated_wallet = ((old_language == crypto::ElectrumWords::old_language_name) ||
+ crypto::ElectrumWords::get_is_old_style_seed(req.seed));
+
+ std::string mnemonic_language = old_language;
+ if (was_deprecated_wallet)
+ {
+ // The user had used an older version of the wallet with old style mnemonics.
+ res.was_deprecated = true;
+ }
+
+ if (old_language == crypto::ElectrumWords::old_language_name)
+ {
+ if (req.language.empty())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Wallet was using the old seed language. You need to specify a new seed language.";
+ return false;
+ }
+ std::vector<std::string> language_list;
+ std::vector<std::string> language_list_en;
+ crypto::ElectrumWords::get_language_list(language_list);
+ crypto::ElectrumWords::get_language_list(language_list_en, true);
+ if (std::find(language_list.begin(), language_list.end(), req.language) == language_list.end() &&
+ std::find(language_list_en.begin(), language_list_en.end(), req.language) == language_list_en.end())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Wallet was using the old seed language, and the specified new seed language is invalid.";
+ return false;
+ }
+ mnemonic_language = req.language;
+ }
+
+ wal->set_seed_language(mnemonic_language);
+
+ crypto::secret_key recovery_val;
+ try
+ {
+ recovery_val = wal->generate(wallet_file, std::move(rc.second).password(), recovery_key, true, false, false);
+ MINFO("Wallet has been restored.\n");
+ }
+ catch (const std::exception &e)
+ {
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
+ return false;
+ }
+
+ // // Convert the secret key back to seed
+ epee::wipeable_string electrum_words;
+ if (!crypto::ElectrumWords::bytes_to_words(recovery_val, electrum_words, mnemonic_language))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to encode seed";
+ return false;
+ }
+ res.seed = electrum_words.data();
+
+ if (!wal)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to generate wallet";
+ return false;
+ }
+
+ // set blockheight if given
+ try
+ {
+ wal->set_refresh_from_block_height(req.restore_height);
+ wal->rewrite(wallet_file, password);
+ }
+ catch (const std::exception &e)
+ {
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
+ return false;
+ }
+
+ if (m_wallet)
+ {
+ try
+ {
+ m_wallet->store();
+ }
+ catch (const std::exception &e)
+ {
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
+ return false;
+ }
+ delete m_wallet;
+ }
+ m_wallet = wal.release();
+ res.address = m_wallet->get_account().get_public_address_str(m_wallet->nettype());
+ res.info = "Wallet has been restored successfully.";
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_is_multisig(const wallet_rpc::COMMAND_RPC_IS_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IS_MULTISIG::response& res, epee::json_rpc::error& er)
{
if (!m_wallet) return not_open(er);
diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h
index 887723ed5..abbbe82c5 100644
--- a/src/wallet/wallet_rpc_server.h
+++ b/src/wallet/wallet_rpc_server.h
@@ -137,6 +137,7 @@ namespace tools
MAP_JON_RPC_WE("open_wallet", on_open_wallet, wallet_rpc::COMMAND_RPC_OPEN_WALLET)
MAP_JON_RPC_WE("close_wallet", on_close_wallet, wallet_rpc::COMMAND_RPC_CLOSE_WALLET)
MAP_JON_RPC_WE("change_wallet_password", on_change_wallet_password, wallet_rpc::COMMAND_RPC_CHANGE_WALLET_PASSWORD)
+ MAP_JON_RPC_WE("restore_deterministic_wallet", on_restore_deterministic_wallet, wallet_rpc::COMMAND_RPC_RESTORE_DETERMINISTIC_WALLET)
MAP_JON_RPC_WE("is_multisig", on_is_multisig, wallet_rpc::COMMAND_RPC_IS_MULTISIG)
MAP_JON_RPC_WE("prepare_multisig", on_prepare_multisig, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG)
MAP_JON_RPC_WE("make_multisig", on_make_multisig, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG)
@@ -216,6 +217,7 @@ namespace tools
bool on_open_wallet(const wallet_rpc::COMMAND_RPC_OPEN_WALLET::request& req, wallet_rpc::COMMAND_RPC_OPEN_WALLET::response& res, epee::json_rpc::error& er);
bool on_close_wallet(const wallet_rpc::COMMAND_RPC_CLOSE_WALLET::request& req, wallet_rpc::COMMAND_RPC_CLOSE_WALLET::response& res, epee::json_rpc::error& er);
bool on_change_wallet_password(const wallet_rpc::COMMAND_RPC_CHANGE_WALLET_PASSWORD::request& req, wallet_rpc::COMMAND_RPC_CHANGE_WALLET_PASSWORD::response& res, epee::json_rpc::error& er);
+ bool on_restore_deterministic_wallet(const wallet_rpc::COMMAND_RPC_RESTORE_DETERMINISTIC_WALLET::request& req, wallet_rpc::COMMAND_RPC_RESTORE_DETERMINISTIC_WALLET::response& res, epee::json_rpc::error& er);
bool on_is_multisig(const wallet_rpc::COMMAND_RPC_IS_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IS_MULTISIG::response& res, epee::json_rpc::error& er);
bool on_prepare_multisig(const wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::response& res, epee::json_rpc::error& er);
bool on_make_multisig(const wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::response& res, epee::json_rpc::error& er);
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index 924f3a0f1..afb8c6e91 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -47,7 +47,7 @@
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define WALLET_RPC_VERSION_MAJOR 1
-#define WALLET_RPC_VERSION_MINOR 5
+#define WALLET_RPC_VERSION_MINOR 6
#define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR)
namespace tools
@@ -1057,7 +1057,10 @@ namespace wallet_rpc
{
struct request
{
+ bool hard;
+
BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE_OPT(hard, false);
END_KV_SERIALIZE_MAP()
};
@@ -1579,9 +1582,11 @@ namespace wallet_rpc
struct response
{
+ uint32_t offset;
std::vector<signed_key_image> signed_key_images;
BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(offset);
KV_SERIALIZE(signed_key_images);
END_KV_SERIALIZE_MAP()
};
@@ -1602,9 +1607,11 @@ namespace wallet_rpc
struct request
{
+ uint32_t offset;
std::vector<signed_key_image> signed_key_images;
BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE_OPT(offset, (uint32_t)0);
KV_SERIALIZE(signed_key_images);
END_KV_SERIALIZE_MAP()
};
@@ -1924,6 +1931,43 @@ namespace wallet_rpc
};
};
+ struct COMMAND_RPC_RESTORE_DETERMINISTIC_WALLET
+ {
+ struct request
+ {
+ uint64_t restore_height;
+ std::string filename;
+ std::string seed;
+ std::string seed_offset;
+ std::string password;
+ std::string language;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE_OPT(restore_height, (uint64_t)0)
+ KV_SERIALIZE(filename)
+ KV_SERIALIZE(seed)
+ KV_SERIALIZE(seed_offset)
+ KV_SERIALIZE(password)
+ KV_SERIALIZE(language)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ std::string address;
+ std::string seed;
+ std::string info;
+ bool was_deprecated;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(seed)
+ KV_SERIALIZE(info)
+ KV_SERIALIZE(was_deprecated)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
struct COMMAND_RPC_IS_MULTISIG
{
struct request