4173 lines
192 KiB
Diff
4173 lines
192 KiB
Diff
From c7da75fee9bea42a6a741905cf6212db5ea8c54b Mon Sep 17 00:00:00 2001
|
|
From: Czarek Nakamoto <cyjan@mrcyjanek.net>
|
|
Date: Tue, 12 Mar 2024 10:02:23 +0100
|
|
Subject: [PATCH] PATCH: background-sync
|
|
|
|
---
|
|
src/cryptonote_basic/account.cpp | 11 +
|
|
src/cryptonote_basic/account.h | 1 +
|
|
src/cryptonote_config.h | 3 +
|
|
src/simplewallet/simplewallet.cpp | 205 +++-
|
|
src/simplewallet/simplewallet.h | 1 +
|
|
src/wallet/api/wallet.cpp | 213 ++++-
|
|
src/wallet/api/wallet.h | 12 +
|
|
src/wallet/api/wallet2_api.h | 42 +
|
|
src/wallet/wallet2.cpp | 948 ++++++++++++++++++-
|
|
src/wallet/wallet2.h | 142 ++-
|
|
src/wallet/wallet_errors.h | 39 +
|
|
src/wallet/wallet_rpc_server.cpp | 162 ++++
|
|
src/wallet/wallet_rpc_server.h | 6 +
|
|
src/wallet/wallet_rpc_server_commands_defs.h | 64 ++
|
|
src/wallet/wallet_rpc_server_error_codes.h | 2 +
|
|
tests/functional_tests/transfer.py | 401 +++++++-
|
|
tests/functional_tests/util_resources.py | 25 +
|
|
tests/functional_tests/wallet.py | 43 +-
|
|
tests/unit_tests/wipeable_string.cpp | 12 +
|
|
utils/python-rpc/framework/wallet.py | 35 +
|
|
20 files changed, 2281 insertions(+), 86 deletions(-)
|
|
|
|
diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp
|
|
index 4931c3740..2d556f285 100644
|
|
--- a/src/cryptonote_basic/account.cpp
|
|
+++ b/src/cryptonote_basic/account.cpp
|
|
@@ -158,6 +158,17 @@ DISABLE_VS_WARNINGS(4244 4345)
|
|
m_keys.m_passphrase.wipe();
|
|
}
|
|
//-----------------------------------------------------------------
|
|
+ void account_base::set_spend_key(const crypto::secret_key& spend_secret_key)
|
|
+ {
|
|
+ // make sure derived spend public key matches saved public spend key
|
|
+ crypto::public_key spend_public_key;
|
|
+ crypto::secret_key_to_public_key(spend_secret_key, spend_public_key);
|
|
+ CHECK_AND_ASSERT_THROW_MES(m_keys.m_account_address.m_spend_public_key == spend_public_key,
|
|
+ "Unexpected derived public spend key");
|
|
+
|
|
+ m_keys.m_spend_secret_key = spend_secret_key;
|
|
+ }
|
|
+ //-----------------------------------------------------------------
|
|
crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random)
|
|
{
|
|
crypto::secret_key first = generate_keys(m_keys.m_account_address.m_spend_public_key, m_keys.m_spend_secret_key, recovery_key, recover);
|
|
diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h
|
|
index 0099ebfe7..1f76febce 100644
|
|
--- a/src/cryptonote_basic/account.h
|
|
+++ b/src/cryptonote_basic/account.h
|
|
@@ -101,6 +101,7 @@ namespace cryptonote
|
|
bool store(const std::string& file_path);
|
|
|
|
void forget_spend_key();
|
|
+ void set_spend_key(const crypto::secret_key& spend_secret_key);
|
|
const std::vector<crypto::secret_key> &get_multisig_keys() const { return m_keys.m_multisig_keys; }
|
|
|
|
void encrypt_keys(const crypto::chacha_key &key) { m_keys.encrypt(key); }
|
|
diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h
|
|
index 8e1a07110..cf50b2adc 100644
|
|
--- a/src/cryptonote_config.h
|
|
+++ b/src/cryptonote_config.h
|
|
@@ -233,6 +233,7 @@ namespace config
|
|
} }; // Bender's nightmare
|
|
std::string const GENESIS_TX = "013c01ff0001ffffffffffff03029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121017767aafcde9be00dcfd098715ebcf7f410daebc582fda69d24a28e9d0bc890d1";
|
|
uint32_t const GENESIS_NONCE = 10000;
|
|
+ std::string const BACKGROUND_WALLET_SUFFIX = ".background";
|
|
|
|
// Hash domain separators
|
|
const char HASH_KEY_BULLETPROOF_EXPONENT[] = "bulletproof";
|
|
@@ -243,6 +244,8 @@ namespace config
|
|
const unsigned char HASH_KEY_ENCRYPTED_PAYMENT_ID = 0x8d;
|
|
const unsigned char HASH_KEY_WALLET = 0x8c;
|
|
const unsigned char HASH_KEY_WALLET_CACHE = 0x8d;
|
|
+ const unsigned char HASH_KEY_BACKGROUND_CACHE = 0x8e;
|
|
+ const unsigned char HASH_KEY_BACKGROUND_KEYS_FILE = 0x8f;
|
|
const unsigned char HASH_KEY_RPC_PAYMENT_NONCE = 0x58;
|
|
const unsigned char HASH_KEY_MEMORY = 'k';
|
|
const unsigned char HASH_KEY_MULTISIG[] = {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
|
|
index f2eaf6b13..341b0e448 100644
|
|
--- a/src/simplewallet/simplewallet.cpp
|
|
+++ b/src/simplewallet/simplewallet.cpp
|
|
@@ -155,6 +155,17 @@ typedef cryptonote::simple_wallet sw;
|
|
} \
|
|
} while(0)
|
|
|
|
+#define CHECK_IF_BACKGROUND_SYNCING(msg) \
|
|
+ do \
|
|
+ { \
|
|
+ if (m_wallet->is_background_wallet() || m_wallet->is_background_syncing()) \
|
|
+ { \
|
|
+ std::string type = m_wallet->is_background_wallet() ? "background wallet" : "background syncing wallet"; \
|
|
+ fail_msg_writer() << boost::format(tr("%s %s")) % type % msg; \
|
|
+ return false; \
|
|
+ } \
|
|
+ } while (0)
|
|
+
|
|
enum TransferType {
|
|
Transfer,
|
|
TransferLocked,
|
|
@@ -332,7 +343,7 @@ namespace
|
|
auto pwd_container = tools::password_container::prompt(verify, prompt);
|
|
if (!pwd_container)
|
|
{
|
|
- tools::fail_msg_writer() << sw::tr("failed to read wallet password");
|
|
+ tools::fail_msg_writer() << sw::tr("failed to read password");
|
|
}
|
|
return pwd_container;
|
|
}
|
|
@@ -342,6 +353,11 @@ namespace
|
|
return password_prompter(verify ? sw::tr("Enter a new password for the wallet") : sw::tr("Wallet password"), verify);
|
|
}
|
|
|
|
+ boost::optional<tools::password_container> background_sync_cache_password_prompter(bool verify)
|
|
+ {
|
|
+ return password_prompter(verify ? sw::tr("Enter a custom password for the background sync cache") : sw::tr("Background sync cache password"), verify);
|
|
+ }
|
|
+
|
|
inline std::string interpret_rpc_response(bool ok, const std::string& status)
|
|
{
|
|
std::string err;
|
|
@@ -459,6 +475,41 @@ namespace
|
|
return "invalid";
|
|
}
|
|
|
|
+ const struct
|
|
+ {
|
|
+ const char *name;
|
|
+ tools::wallet2::BackgroundSyncType background_sync_type;
|
|
+ } background_sync_type_names[] =
|
|
+ {
|
|
+ { "off", tools::wallet2::BackgroundSyncOff },
|
|
+ { "reuse-wallet-password", tools::wallet2::BackgroundSyncReusePassword },
|
|
+ { "custom-background-password", tools::wallet2::BackgroundSyncCustomPassword },
|
|
+ };
|
|
+
|
|
+ bool parse_background_sync_type(const std::string &s, tools::wallet2::BackgroundSyncType &background_sync_type)
|
|
+ {
|
|
+ for (size_t n = 0; n < sizeof(background_sync_type_names) / sizeof(background_sync_type_names[0]); ++n)
|
|
+ {
|
|
+ if (s == background_sync_type_names[n].name)
|
|
+ {
|
|
+ background_sync_type = background_sync_type_names[n].background_sync_type;
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+ fail_msg_writer() << cryptonote::simple_wallet::tr("failed to parse background sync type");
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ std::string get_background_sync_type_name(tools::wallet2::BackgroundSyncType type)
|
|
+ {
|
|
+ for (size_t n = 0; n < sizeof(background_sync_type_names) / sizeof(background_sync_type_names[0]); ++n)
|
|
+ {
|
|
+ if (type == background_sync_type_names[n].background_sync_type)
|
|
+ return background_sync_type_names[n].name;
|
|
+ }
|
|
+ return "invalid";
|
|
+ }
|
|
+
|
|
std::string get_version_string(uint32_t version)
|
|
{
|
|
return boost::lexical_cast<std::string>(version >> 16) + "." + boost::lexical_cast<std::string>(version & 0xffff);
|
|
@@ -812,6 +863,7 @@ bool simple_wallet::spendkey(const std::vector<std::string> &args/* = std::vecto
|
|
fail_msg_writer() << tr("wallet is watch-only and has no spend key");
|
|
return true;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING("has no spend key");
|
|
// don't log
|
|
PAUSE_READLINE();
|
|
if (m_wallet->key_on_device()) {
|
|
@@ -843,6 +895,7 @@ bool simple_wallet::print_seed(bool encrypted)
|
|
fail_msg_writer() << tr("wallet is watch-only and has no seed");
|
|
return true;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING("has no seed");
|
|
|
|
multisig = m_wallet->multisig(&ready);
|
|
if (multisig)
|
|
@@ -920,6 +973,7 @@ bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = s
|
|
fail_msg_writer() << tr("wallet is watch-only and has no seed");
|
|
return true;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING("has no seed");
|
|
|
|
epee::wipeable_string password;
|
|
{
|
|
@@ -1066,6 +1120,7 @@ bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args,
|
|
fail_msg_writer() << tr("wallet is watch-only and cannot be made multisig");
|
|
return false;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot be made multisig");
|
|
|
|
if(m_wallet->get_num_transfer_details())
|
|
{
|
|
@@ -2202,6 +2257,7 @@ bool simple_wallet::save_known_rings(const std::vector<std::string> &args)
|
|
|
|
bool simple_wallet::freeze_thaw(const std::vector<std::string> &args, bool freeze)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot freeze/thaw");
|
|
if (args.empty())
|
|
{
|
|
fail_msg_writer() << boost::format(tr("usage: %s <key_image>|<pubkey>")) % (freeze ? "freeze" : "thaw");
|
|
@@ -2241,6 +2297,7 @@ bool simple_wallet::thaw(const std::vector<std::string> &args)
|
|
|
|
bool simple_wallet::frozen(const std::vector<std::string> &args)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot see frozen key images");
|
|
if (args.empty())
|
|
{
|
|
size_t ntd = m_wallet->get_num_transfer_details();
|
|
@@ -3012,6 +3069,56 @@ bool simple_wallet::set_track_uses(const std::vector<std::string> &args/* = std:
|
|
return true;
|
|
}
|
|
|
|
+bool simple_wallet::setup_background_sync(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
|
|
+{
|
|
+ if (m_wallet->multisig())
|
|
+ {
|
|
+ fail_msg_writer() << tr("background sync not implemented for multisig wallet");
|
|
+ return true;
|
|
+ }
|
|
+ if (m_wallet->watch_only())
|
|
+ {
|
|
+ fail_msg_writer() << tr("background sync not implemented for watch only wallet");
|
|
+ return true;
|
|
+ }
|
|
+ if (m_wallet->key_on_device())
|
|
+ {
|
|
+ fail_msg_writer() << tr("command not supported by HW wallet");
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ const auto pwd_container = get_and_verify_password();
|
|
+ if (pwd_container)
|
|
+ {
|
|
+ tools::wallet2::BackgroundSyncType background_sync_type;
|
|
+ if (!parse_background_sync_type(args[1], background_sync_type))
|
|
+ {
|
|
+ fail_msg_writer() << tr("invalid option");
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ try
|
|
+ {
|
|
+ boost::optional<epee::wipeable_string> background_cache_password = boost::none;
|
|
+ if (background_sync_type == tools::wallet2::BackgroundSyncCustomPassword)
|
|
+ {
|
|
+ const auto background_pwd_container = background_sync_cache_password_prompter(true);
|
|
+ if (!background_pwd_container)
|
|
+ return true;
|
|
+ background_cache_password = background_pwd_container->password();
|
|
+ }
|
|
+
|
|
+ LOCK_IDLE_SCOPE();
|
|
+ m_wallet->setup_background_sync(background_sync_type, pwd_container->password(), background_cache_password);
|
|
+ }
|
|
+ catch (const std::exception &e)
|
|
+ {
|
|
+ fail_msg_writer() << tr("Error setting background sync type: ") << e.what();
|
|
+ }
|
|
+ }
|
|
+ return true;
|
|
+}
|
|
+
|
|
bool simple_wallet::set_show_wallet_name_when_locked(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
|
|
{
|
|
const auto pwd_container = get_and_verify_password();
|
|
@@ -3244,6 +3351,7 @@ bool simple_wallet::apropos(const std::vector<std::string> &args)
|
|
|
|
bool simple_wallet::scan_tx(const std::vector<std::string> &args)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot scan tx");
|
|
if (args.empty())
|
|
{
|
|
PRINT_USAGE(USAGE_SCAN_TX);
|
|
@@ -3473,6 +3581,8 @@ simple_wallet::simple_wallet()
|
|
" Ignore outputs of amount below this threshold when spending.\n "
|
|
"track-uses <1|0>\n "
|
|
" Whether to keep track of owned outputs uses.\n "
|
|
+ "background-sync <off|reuse-wallet-password|custom-background-password>\n "
|
|
+ " Set this to enable scanning in the background with just the view key while the wallet is locked.\n "
|
|
"setup-background-mining <1|0>\n "
|
|
" Whether to enable background mining. Set this to support the network and to get a chance to receive new monero.\n "
|
|
"device-name <device_name[:device_spec]>\n "
|
|
@@ -3891,6 +4001,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
|
|
success_msg_writer() << "ignore-outputs-above = " << cryptonote::print_money(m_wallet->ignore_outputs_above());
|
|
success_msg_writer() << "ignore-outputs-below = " << cryptonote::print_money(m_wallet->ignore_outputs_below());
|
|
success_msg_writer() << "track-uses = " << m_wallet->track_uses();
|
|
+ success_msg_writer() << "background-sync = " << get_background_sync_type_name(m_wallet->background_sync_type());
|
|
success_msg_writer() << "setup-background-mining = " << setup_background_mining_string;
|
|
success_msg_writer() << "device-name = " << m_wallet->device_name();
|
|
success_msg_writer() << "export-format = " << (m_wallet->export_format() == tools::wallet2::ExportFormat::Ascii ? "ascii" : "binary");
|
|
@@ -3909,6 +4020,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
|
|
}
|
|
else
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot change wallet settings");
|
|
|
|
#define CHECK_SIMPLE_VARIABLE(name, f, help) do \
|
|
if (args[0] == name) { \
|
|
@@ -3962,6 +4074,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
|
|
CHECK_SIMPLE_VARIABLE("ignore-outputs-above", set_ignore_outputs_above, tr("amount"));
|
|
CHECK_SIMPLE_VARIABLE("ignore-outputs-below", set_ignore_outputs_below, tr("amount"));
|
|
CHECK_SIMPLE_VARIABLE("track-uses", set_track_uses, tr("0 or 1"));
|
|
+ CHECK_SIMPLE_VARIABLE("background-sync", setup_background_sync, tr("off (default); reuse-wallet-password (reuse the wallet password to encrypt the background cache); custom-background-password (use a custom background password to encrypt the background cache)"));
|
|
CHECK_SIMPLE_VARIABLE("show-wallet-name-when-locked", set_show_wallet_name_when_locked, tr("1 or 0"));
|
|
CHECK_SIMPLE_VARIABLE("inactivity-lock-timeout", set_inactivity_lock_timeout, tr("unsigned integer (seconds, 0 to disable)"));
|
|
CHECK_SIMPLE_VARIABLE("setup-background-mining", set_setup_background_mining, tr("1/yes or 0/no"));
|
|
@@ -4915,7 +5028,10 @@ std::string simple_wallet::get_mnemonic_language()
|
|
//----------------------------------------------------------------------------------------------------
|
|
boost::optional<tools::password_container> simple_wallet::get_and_verify_password() const
|
|
{
|
|
- auto pwd_container = default_password_prompter(m_wallet_file.empty());
|
|
+ const bool verify = m_wallet_file.empty();
|
|
+ auto pwd_container = (m_wallet->is_background_wallet() && m_wallet->background_sync_type() == tools::wallet2::BackgroundSyncCustomPassword)
|
|
+ ? background_sync_cache_password_prompter(verify)
|
|
+ : default_password_prompter(verify);
|
|
if (!pwd_container)
|
|
return boost::none;
|
|
|
|
@@ -5218,6 +5334,8 @@ boost::optional<epee::wipeable_string> simple_wallet::open_wallet(const boost::p
|
|
prefix = tr("Opened watch-only wallet");
|
|
else if (m_wallet->multisig(&ready, &threshold, &total))
|
|
prefix = (boost::format(tr("Opened %u/%u multisig wallet%s")) % threshold % total % (ready ? "" : " (not yet finalized)")).str();
|
|
+ else if (m_wallet->is_background_wallet())
|
|
+ prefix = tr("Opened background wallet");
|
|
else
|
|
prefix = tr("Opened wallet");
|
|
message_writer(console_color_white, true) <<
|
|
@@ -5426,6 +5544,10 @@ void simple_wallet::stop_background_mining()
|
|
//----------------------------------------------------------------------------------------------------
|
|
void simple_wallet::check_background_mining(const epee::wipeable_string &password)
|
|
{
|
|
+ // Background mining can be toggled from the main wallet
|
|
+ if (m_wallet->is_background_wallet() || m_wallet->is_background_syncing())
|
|
+ return;
|
|
+
|
|
tools::wallet2::BackgroundMiningSetupType setup = m_wallet->setup_background_mining();
|
|
if (setup == tools::wallet2::BackgroundMiningNo)
|
|
{
|
|
@@ -6290,6 +6412,7 @@ bool simple_wallet::show_blockchain_height(const std::vector<std::string>& args)
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::rescan_spent(const std::vector<std::string> &args)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot rescan spent");
|
|
if (!m_wallet->is_trusted_daemon())
|
|
{
|
|
fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon");
|
|
@@ -6547,10 +6670,27 @@ void simple_wallet::check_for_inactivity_lock(bool user)
|
|
" || ||" << std::endl <<
|
|
"" << std::endl;
|
|
}
|
|
+
|
|
+ bool started_background_sync = false;
|
|
+ if (!m_wallet->is_background_wallet() &&
|
|
+ m_wallet->background_sync_type() != tools::wallet2::BackgroundSyncOff)
|
|
+ {
|
|
+ LOCK_IDLE_SCOPE();
|
|
+ m_wallet->start_background_sync();
|
|
+ started_background_sync = true;
|
|
+ }
|
|
+
|
|
while (1)
|
|
{
|
|
const char *inactivity_msg = user ? "" : tr("Locked due to inactivity.");
|
|
- tools::msg_writer() << inactivity_msg << (inactivity_msg[0] ? " " : "") << tr("The wallet password is required to unlock the console.");
|
|
+ tools::msg_writer() << inactivity_msg << (inactivity_msg[0] ? " " : "") << (
|
|
+ (m_wallet->is_background_wallet() && m_wallet->background_sync_type() == tools::wallet2::BackgroundSyncCustomPassword)
|
|
+ ? tr("The background password is required to unlock the console.")
|
|
+ : tr("The wallet password is required to unlock the console.")
|
|
+ );
|
|
+
|
|
+ if (m_wallet->is_background_syncing())
|
|
+ tools::msg_writer() << tr("\nSyncing in the background while locked...") << std::endl;
|
|
|
|
const bool show_wallet_name = m_wallet->show_wallet_name_when_locked();
|
|
if (show_wallet_name)
|
|
@@ -6563,8 +6703,16 @@ void simple_wallet::check_for_inactivity_lock(bool user)
|
|
}
|
|
try
|
|
{
|
|
- if (get_and_verify_password())
|
|
+ const auto pwd_container = get_and_verify_password();
|
|
+ if (pwd_container)
|
|
+ {
|
|
+ if (started_background_sync)
|
|
+ {
|
|
+ LOCK_IDLE_SCOPE();
|
|
+ m_wallet->stop_background_sync(pwd_container->password());
|
|
+ }
|
|
break;
|
|
+ }
|
|
}
|
|
catch (...) { /* do nothing, just let the loop loop */ }
|
|
}
|
|
@@ -6591,6 +6739,7 @@ bool simple_wallet::on_command(bool (simple_wallet::*cmd)(const std::vector<std:
|
|
bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_, bool called_by_mms)
|
|
{
|
|
// "transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>]"
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot transfer");
|
|
if (!try_connect_to_daemon())
|
|
return false;
|
|
|
|
@@ -7064,6 +7213,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::transfer(const std::vector<std::string> &args_)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot transfer");
|
|
if (args_.size() < 1)
|
|
{
|
|
PRINT_USAGE(USAGE_TRANSFER);
|
|
@@ -7075,6 +7225,7 @@ bool simple_wallet::transfer(const std::vector<std::string> &args_)
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::locked_transfer(const std::vector<std::string> &args_)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot transfer");
|
|
if (args_.size() < 1)
|
|
{
|
|
PRINT_USAGE(USAGE_LOCKED_TRANSFER);
|
|
@@ -7086,6 +7237,7 @@ bool simple_wallet::locked_transfer(const std::vector<std::string> &args_)
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::locked_sweep_all(const std::vector<std::string> &args_)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep");
|
|
if (args_.size() < 1)
|
|
{
|
|
PRINT_USAGE(USAGE_LOCKED_SWEEP_ALL);
|
|
@@ -7098,6 +7250,7 @@ bool simple_wallet::locked_sweep_all(const std::vector<std::string> &args_)
|
|
|
|
bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep");
|
|
if (!try_connect_to_daemon())
|
|
return true;
|
|
|
|
@@ -7205,6 +7358,7 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_)
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::sweep_main(uint32_t account, uint64_t below, bool locked, const std::vector<std::string> &args_)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep");
|
|
auto print_usage = [this, account, below]()
|
|
{
|
|
if (below)
|
|
@@ -7521,6 +7675,7 @@ bool simple_wallet::sweep_main(uint32_t account, uint64_t below, bool locked, co
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::sweep_single(const std::vector<std::string> &args_)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep");
|
|
if (!try_connect_to_daemon())
|
|
return true;
|
|
|
|
@@ -7759,12 +7914,14 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_)
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::sweep_all(const std::vector<std::string> &args_)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep");
|
|
sweep_main(m_current_subaddress_account, 0, false, args_);
|
|
return true;
|
|
}
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::sweep_account(const std::vector<std::string> &args_)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep");
|
|
auto local_args = args_;
|
|
if (local_args.empty())
|
|
{
|
|
@@ -7785,6 +7942,7 @@ bool simple_wallet::sweep_account(const std::vector<std::string> &args_)
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::sweep_below(const std::vector<std::string> &args_)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep");
|
|
uint64_t below = 0;
|
|
if (args_.size() < 1)
|
|
{
|
|
@@ -7803,6 +7961,7 @@ bool simple_wallet::sweep_below(const std::vector<std::string> &args_)
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::donate(const std::vector<std::string> &args_)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot donate");
|
|
std::vector<std::string> local_args = args_;
|
|
if(local_args.empty() || local_args.size() > 5)
|
|
{
|
|
@@ -7864,6 +8023,7 @@ bool simple_wallet::donate(const std::vector<std::string> &args_)
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, const std::function<const tools::wallet2::tx_construction_data&(size_t)> &get_tx, const std::string &extra_message)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot load tx");
|
|
// gather info to ask the user
|
|
uint64_t amount = 0, amount_to_dests = 0, change = 0;
|
|
size_t min_ring_size = ~0;
|
|
@@ -8044,6 +8204,7 @@ bool simple_wallet::sign_transfer(const std::vector<std::string> &args_)
|
|
fail_msg_writer() << tr("This is a watch only wallet");
|
|
return true;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot sign transfer");
|
|
|
|
bool export_raw = false;
|
|
std::string unsigned_filename = "unsigned_monero_tx";
|
|
@@ -8151,6 +8312,8 @@ std::string get_tx_key_stream(crypto::secret_key tx_key, std::vector<crypto::sec
|
|
|
|
bool simple_wallet::get_tx_key(const std::vector<std::string> &args_)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot get tx key");
|
|
+
|
|
std::vector<std::string> local_args = args_;
|
|
|
|
if (m_wallet->key_on_device() && m_wallet->get_account().get_device().get_type() != hw::device::TREZOR)
|
|
@@ -8191,6 +8354,8 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_)
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::set_tx_key(const std::vector<std::string> &args_)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot set tx key");
|
|
+
|
|
std::vector<std::string> local_args = args_;
|
|
|
|
if(local_args.size() != 2 && local_args.size() != 3) {
|
|
@@ -8267,6 +8432,8 @@ bool simple_wallet::set_tx_key(const std::vector<std::string> &args_)
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::get_tx_proof(const std::vector<std::string> &args)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot get tx proof");
|
|
+
|
|
if (args.size() != 2 && args.size() != 3)
|
|
{
|
|
PRINT_USAGE(USAGE_GET_TX_PROOF);
|
|
@@ -8473,6 +8640,7 @@ bool simple_wallet::check_tx_proof(const std::vector<std::string> &args)
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::get_spend_proof(const std::vector<std::string> &args)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot get spend proof");
|
|
if (m_wallet->key_on_device())
|
|
{
|
|
fail_msg_writer() << tr("command not supported by HW wallet");
|
|
@@ -8557,6 +8725,7 @@ bool simple_wallet::check_spend_proof(const std::vector<std::string> &args)
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::get_reserve_proof(const std::vector<std::string> &args)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot get reserve proof");
|
|
if (m_wallet->key_on_device())
|
|
{
|
|
fail_msg_writer() << tr("command not supported by HW wallet");
|
|
@@ -9243,6 +9412,8 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_)
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::rescan_blockchain(const std::vector<std::string> &args_)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot rescan");
|
|
+
|
|
uint64_t start_height = 0;
|
|
ResetType reset_type = ResetSoft;
|
|
|
|
@@ -9540,6 +9711,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector
|
|
if (command == "new")
|
|
{
|
|
// create a new account and switch to it
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot create new account");
|
|
std::string label = boost::join(local_args, " ");
|
|
if (label.empty())
|
|
label = tr("(Untitled account)");
|
|
@@ -9570,6 +9742,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector
|
|
else if (command == "label" && local_args.size() >= 1)
|
|
{
|
|
// set label of the specified account
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot modify account");
|
|
uint32_t index_major;
|
|
if (!epee::string_tools::get_xtype_from_string(index_major, local_args[0]))
|
|
{
|
|
@@ -9591,6 +9764,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector
|
|
}
|
|
else if (command == "tag" && local_args.size() >= 2)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot modify account");
|
|
const std::string tag = local_args[0];
|
|
std::set<uint32_t> account_indices;
|
|
for (size_t i = 1; i < local_args.size(); ++i)
|
|
@@ -9615,6 +9789,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector
|
|
}
|
|
else if (command == "untag" && local_args.size() >= 1)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot modify account");
|
|
std::set<uint32_t> account_indices;
|
|
for (size_t i = 0; i < local_args.size(); ++i)
|
|
{
|
|
@@ -9638,6 +9813,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector
|
|
}
|
|
else if (command == "tag_description" && local_args.size() >= 1)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot modify account");
|
|
const std::string tag = local_args[0];
|
|
std::string description;
|
|
if (local_args.size() > 1)
|
|
@@ -9755,6 +9931,7 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::
|
|
}
|
|
else if (local_args[0] == "new")
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot add address");
|
|
local_args.erase(local_args.begin());
|
|
std::string label;
|
|
if (local_args.size() > 0)
|
|
@@ -9767,6 +9944,7 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::
|
|
}
|
|
else if (local_args[0] == "mnew")
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot add addresses");
|
|
local_args.erase(local_args.begin());
|
|
if (local_args.size() != 1)
|
|
{
|
|
@@ -9792,6 +9970,7 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::
|
|
}
|
|
else if (local_args[0] == "one-off")
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot add address");
|
|
local_args.erase(local_args.begin());
|
|
std::string label;
|
|
if (local_args.size() != 2)
|
|
@@ -9810,6 +9989,7 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::
|
|
}
|
|
else if (local_args.size() >= 2 && local_args[0] == "label")
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot modify address");
|
|
if (!epee::string_tools::get_xtype_from_string(index, local_args[1]))
|
|
{
|
|
fail_msg_writer() << tr("failed to parse index: ") << local_args[1];
|
|
@@ -9956,6 +10136,8 @@ bool simple_wallet::print_integrated_address(const std::vector<std::string> &arg
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot get address book");
|
|
+
|
|
if (args.size() == 0)
|
|
{
|
|
}
|
|
@@ -10016,6 +10198,8 @@ bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::v
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::set_tx_note(const std::vector<std::string> &args)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot set tx note");
|
|
+
|
|
if (args.size() == 0)
|
|
{
|
|
PRINT_USAGE(USAGE_SET_TX_NOTE);
|
|
@@ -10044,6 +10228,8 @@ bool simple_wallet::set_tx_note(const std::vector<std::string> &args)
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::get_tx_note(const std::vector<std::string> &args)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot get tx note");
|
|
+
|
|
if (args.size() != 1)
|
|
{
|
|
PRINT_USAGE(USAGE_GET_TX_NOTE);
|
|
@@ -10069,6 +10255,8 @@ bool simple_wallet::get_tx_note(const std::vector<std::string> &args)
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::set_description(const std::vector<std::string> &args)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot set description");
|
|
+
|
|
// 0 arguments allowed, for setting the description to empty string
|
|
|
|
std::string description = "";
|
|
@@ -10085,6 +10273,8 @@ bool simple_wallet::set_description(const std::vector<std::string> &args)
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::get_description(const std::vector<std::string> &args)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot get description");
|
|
+
|
|
if (args.size() != 0)
|
|
{
|
|
PRINT_USAGE(USAGE_GET_DESCRIPTION);
|
|
@@ -10143,6 +10333,8 @@ bool simple_wallet::wallet_info(const std::vector<std::string> &args)
|
|
type = tr("Watch only");
|
|
else if (m_wallet->multisig(&ready, &threshold, &total))
|
|
type = (boost::format(tr("%u/%u multisig%s")) % threshold % total % (ready ? "" : " (not yet finalized)")).str();
|
|
+ else if (m_wallet->is_background_wallet())
|
|
+ type = tr("Background wallet");
|
|
else
|
|
type = tr("Normal");
|
|
message_writer() << tr("Type: ") << type;
|
|
@@ -10154,6 +10346,7 @@ bool simple_wallet::wallet_info(const std::vector<std::string> &args)
|
|
//----------------------------------------------------------------------------------------------------
|
|
bool simple_wallet::sign(const std::vector<std::string> &args)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot sign");
|
|
if (m_wallet->key_on_device())
|
|
{
|
|
fail_msg_writer() << tr("command not supported by HW wallet");
|
|
@@ -10261,6 +10454,7 @@ bool simple_wallet::export_key_images(const std::vector<std::string> &args_)
|
|
fail_msg_writer() << tr("command not supported by HW wallet");
|
|
return true;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot export key images");
|
|
auto args = args_;
|
|
|
|
if (m_wallet->watch_only())
|
|
@@ -10314,6 +10508,7 @@ bool simple_wallet::import_key_images(const std::vector<std::string> &args)
|
|
fail_msg_writer() << tr("command not supported by HW wallet");
|
|
return true;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot import key images");
|
|
if (!m_wallet->is_trusted_daemon())
|
|
{
|
|
fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon");
|
|
@@ -10422,6 +10617,7 @@ bool simple_wallet::export_outputs(const std::vector<std::string> &args_)
|
|
fail_msg_writer() << tr("command not supported by HW wallet");
|
|
return true;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot export outputs");
|
|
auto args = args_;
|
|
|
|
bool all = false;
|
|
@@ -10471,6 +10667,7 @@ bool simple_wallet::import_outputs(const std::vector<std::string> &args)
|
|
fail_msg_writer() << tr("command not supported by HW wallet");
|
|
return true;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING("cannot import outputs");
|
|
if (args.size() != 1)
|
|
{
|
|
PRINT_USAGE(USAGE_IMPORT_OUTPUTS);
|
|
diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h
|
|
index 7c45d45e8..79c739cdd 100644
|
|
--- a/src/simplewallet/simplewallet.h
|
|
+++ b/src/simplewallet/simplewallet.h
|
|
@@ -147,6 +147,7 @@ namespace cryptonote
|
|
bool set_ignore_outputs_above(const std::vector<std::string> &args = std::vector<std::string>());
|
|
bool set_ignore_outputs_below(const std::vector<std::string> &args = std::vector<std::string>());
|
|
bool set_track_uses(const std::vector<std::string> &args = std::vector<std::string>());
|
|
+ bool setup_background_sync(const std::vector<std::string> &args = std::vector<std::string>());
|
|
bool set_show_wallet_name_when_locked(const std::vector<std::string> &args = std::vector<std::string>());
|
|
bool set_inactivity_lock_timeout(const std::vector<std::string> &args = std::vector<std::string>());
|
|
bool set_setup_background_mining(const std::vector<std::string> &args = std::vector<std::string>());
|
|
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
|
|
index 472f05016..1a9c6f674 100644
|
|
--- a/src/wallet/api/wallet.cpp
|
|
+++ b/src/wallet/api/wallet.cpp
|
|
@@ -54,6 +54,40 @@ using namespace cryptonote;
|
|
#undef MONERO_DEFAULT_LOG_CATEGORY
|
|
#define MONERO_DEFAULT_LOG_CATEGORY "WalletAPI"
|
|
|
|
+#define LOCK_REFRESH() \
|
|
+ bool refresh_enabled = m_refreshEnabled; \
|
|
+ m_refreshEnabled = false; \
|
|
+ m_wallet->stop(); \
|
|
+ m_refreshCV.notify_one(); \
|
|
+ boost::mutex::scoped_lock lock(m_refreshMutex); \
|
|
+ boost::mutex::scoped_lock lock2(m_refreshMutex2); \
|
|
+ epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ \
|
|
+ /* m_refreshMutex's still locked here */ \
|
|
+ if (refresh_enabled) \
|
|
+ startRefresh(); \
|
|
+ })
|
|
+
|
|
+#define PRE_VALIDATE_BACKGROUND_SYNC() \
|
|
+ do \
|
|
+ { \
|
|
+ clearStatus(); \
|
|
+ if (m_wallet->key_on_device()) \
|
|
+ { \
|
|
+ setStatusError(tr("HW wallet cannot use background sync")); \
|
|
+ return false; \
|
|
+ } \
|
|
+ if (m_wallet->watch_only()) \
|
|
+ { \
|
|
+ setStatusError(tr("View only wallet cannot use background sync")); \
|
|
+ return false; \
|
|
+ } \
|
|
+ if (m_wallet->multisig()) \
|
|
+ { \
|
|
+ setStatusError(tr("Multisig wallet cannot use background sync")); \
|
|
+ return false; \
|
|
+ } \
|
|
+ } while (0)
|
|
+
|
|
namespace Monero {
|
|
|
|
namespace {
|
|
@@ -814,6 +848,8 @@ bool WalletImpl::close(bool store)
|
|
|
|
std::string WalletImpl::seed(const std::string& seed_offset) const
|
|
{
|
|
+ if (checkBackgroundSync("cannot get seed"))
|
|
+ return std::string();
|
|
epee::wipeable_string seed;
|
|
if (m_wallet)
|
|
m_wallet->get_seed(seed, seed_offset);
|
|
@@ -876,6 +912,8 @@ std::string WalletImpl::getSeedLanguage() const
|
|
|
|
void WalletImpl::setSeedLanguage(const std::string &arg)
|
|
{
|
|
+ if (checkBackgroundSync("cannot set seed language"))
|
|
+ return;
|
|
m_wallet->set_seed_language(arg);
|
|
}
|
|
|
|
@@ -899,6 +937,8 @@ void WalletImpl::statusWithErrorString(int& status, std::string& errorString) co
|
|
|
|
bool WalletImpl::setPassword(const std::string &password)
|
|
{
|
|
+ if (checkBackgroundSync("cannot change password"))
|
|
+ return false;
|
|
clearStatus();
|
|
try {
|
|
m_wallet->change_password(m_wallet->get_wallet_file(), m_password, password);
|
|
@@ -1059,6 +1099,8 @@ bool WalletImpl::lightWalletImportWalletRequest(std::string &payment_id, uint64_
|
|
|
|
void WalletImpl::setRefreshFromBlockHeight(uint64_t refresh_from_block_height)
|
|
{
|
|
+ if (checkBackgroundSync("cannot change refresh height"))
|
|
+ return;
|
|
m_wallet->set_refresh_from_block_height(refresh_from_block_height);
|
|
}
|
|
|
|
@@ -1176,6 +1218,8 @@ void WalletImpl::refreshAsync()
|
|
|
|
bool WalletImpl::rescanBlockchain()
|
|
{
|
|
+ if (checkBackgroundSync("cannot rescan blockchain"))
|
|
+ return false;
|
|
clearStatus();
|
|
m_refreshShouldRescan = true;
|
|
doRefresh();
|
|
@@ -1184,6 +1228,8 @@ bool WalletImpl::rescanBlockchain()
|
|
|
|
void WalletImpl::rescanBlockchainAsync()
|
|
{
|
|
+ if (checkBackgroundSync("cannot rescan blockchain"))
|
|
+ return;
|
|
m_refreshShouldRescan = true;
|
|
refreshAsync();
|
|
}
|
|
@@ -1207,7 +1253,7 @@ int WalletImpl::autoRefreshInterval() const
|
|
UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_filename) {
|
|
clearStatus();
|
|
UnsignedTransactionImpl * transaction = new UnsignedTransactionImpl(*this);
|
|
- if (!m_wallet->load_unsigned_tx(unsigned_filename, transaction->m_unsigned_tx_set)){
|
|
+ if (checkBackgroundSync("cannot load tx") || !m_wallet->load_unsigned_tx(unsigned_filename, transaction->m_unsigned_tx_set)){
|
|
setStatusError(tr("Failed to load unsigned transactions"));
|
|
transaction->m_status = UnsignedTransaction::Status::Status_Error;
|
|
transaction->m_errorString = errorString();
|
|
@@ -1227,6 +1273,8 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file
|
|
|
|
bool WalletImpl::submitTransaction(const string &fileName) {
|
|
clearStatus();
|
|
+ if (checkBackgroundSync("cannot submit tx"))
|
|
+ return false;
|
|
std::unique_ptr<PendingTransactionImpl> transaction(new PendingTransactionImpl(*this));
|
|
|
|
bool r = m_wallet->load_tx(fileName, transaction->m_pending_tx);
|
|
@@ -1250,6 +1298,8 @@ bool WalletImpl::exportKeyImages(const string &filename, bool all)
|
|
setStatusError(tr("Wallet is view only"));
|
|
return false;
|
|
}
|
|
+ if (checkBackgroundSync("cannot export key images"))
|
|
+ return false;
|
|
|
|
try
|
|
{
|
|
@@ -1270,6 +1320,8 @@ bool WalletImpl::exportKeyImages(const string &filename, bool all)
|
|
|
|
bool WalletImpl::importKeyImages(const string &filename)
|
|
{
|
|
+ if (checkBackgroundSync("cannot import key images"))
|
|
+ return false;
|
|
if (!trustedDaemon()) {
|
|
setStatusError(tr("Key images can only be imported with a trusted daemon"));
|
|
return false;
|
|
@@ -1293,6 +1345,8 @@ bool WalletImpl::importKeyImages(const string &filename)
|
|
|
|
bool WalletImpl::exportOutputs(const string &filename, bool all)
|
|
{
|
|
+ if (checkBackgroundSync("cannot export outputs"))
|
|
+ return false;
|
|
if (m_wallet->key_on_device())
|
|
{
|
|
setStatusError(string(tr("Not supported on HW wallets.")) + filename);
|
|
@@ -1323,6 +1377,8 @@ bool WalletImpl::exportOutputs(const string &filename, bool all)
|
|
|
|
bool WalletImpl::importOutputs(const string &filename)
|
|
{
|
|
+ if (checkBackgroundSync("cannot import outputs"))
|
|
+ return false;
|
|
if (m_wallet->key_on_device())
|
|
{
|
|
setStatusError(string(tr("Not supported on HW wallets.")) + filename);
|
|
@@ -1355,6 +1411,8 @@ bool WalletImpl::importOutputs(const string &filename)
|
|
|
|
bool WalletImpl::scanTransactions(const std::vector<std::string> &txids)
|
|
{
|
|
+ if (checkBackgroundSync("cannot scan transactions"))
|
|
+ return false;
|
|
if (txids.empty())
|
|
{
|
|
setStatusError(string(tr("Failed to scan transactions: no transaction ids provided.")));
|
|
@@ -1393,8 +1451,86 @@ bool WalletImpl::scanTransactions(const std::vector<std::string> &txids)
|
|
return true;
|
|
}
|
|
|
|
+bool WalletImpl::setupBackgroundSync(const Wallet::BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional<std::string> &background_cache_password)
|
|
+{
|
|
+ try
|
|
+ {
|
|
+ PRE_VALIDATE_BACKGROUND_SYNC();
|
|
+
|
|
+ tools::wallet2::BackgroundSyncType bgs_type;
|
|
+ switch (background_sync_type)
|
|
+ {
|
|
+ case Wallet::BackgroundSync_Off: bgs_type = tools::wallet2::BackgroundSyncOff; break;
|
|
+ case Wallet::BackgroundSync_ReusePassword: bgs_type = tools::wallet2::BackgroundSyncReusePassword; break;
|
|
+ case Wallet::BackgroundSync_CustomPassword: bgs_type = tools::wallet2::BackgroundSyncCustomPassword; break;
|
|
+ default: setStatusError(tr("Unknown background sync type")); return false;
|
|
+ }
|
|
+
|
|
+ boost::optional<epee::wipeable_string> bgc_password = background_cache_password
|
|
+ ? boost::optional<epee::wipeable_string>(*background_cache_password)
|
|
+ : boost::none;
|
|
+
|
|
+ LOCK_REFRESH();
|
|
+ m_wallet->setup_background_sync(bgs_type, wallet_password, bgc_password);
|
|
+ }
|
|
+ catch (const std::exception &e)
|
|
+ {
|
|
+ LOG_ERROR("Failed to setup background sync: " << e.what());
|
|
+ setStatusError(string(tr("Failed to setup background sync: ")) + e.what());
|
|
+ return false;
|
|
+ }
|
|
+ return true;
|
|
+}
|
|
+
|
|
+Wallet::BackgroundSyncType WalletImpl::getBackgroundSyncType() const
|
|
+{
|
|
+ switch (m_wallet->background_sync_type())
|
|
+ {
|
|
+ case tools::wallet2::BackgroundSyncOff: return Wallet::BackgroundSync_Off;
|
|
+ case tools::wallet2::BackgroundSyncReusePassword: return Wallet::BackgroundSync_ReusePassword;
|
|
+ case tools::wallet2::BackgroundSyncCustomPassword: return Wallet::BackgroundSync_CustomPassword;
|
|
+ default: setStatusError(tr("Unknown background sync type")); return Wallet::BackgroundSync_Off;
|
|
+ }
|
|
+}
|
|
+
|
|
+bool WalletImpl::startBackgroundSync()
|
|
+{
|
|
+ try
|
|
+ {
|
|
+ PRE_VALIDATE_BACKGROUND_SYNC();
|
|
+ LOCK_REFRESH();
|
|
+ m_wallet->start_background_sync();
|
|
+ }
|
|
+ catch (const std::exception &e)
|
|
+ {
|
|
+ LOG_ERROR("Failed to start background sync: " << e.what());
|
|
+ setStatusError(string(tr("Failed to start background sync: ")) + e.what());
|
|
+ return false;
|
|
+ }
|
|
+ return true;
|
|
+}
|
|
+
|
|
+bool WalletImpl::stopBackgroundSync(const std::string &wallet_password)
|
|
+{
|
|
+ try
|
|
+ {
|
|
+ PRE_VALIDATE_BACKGROUND_SYNC();
|
|
+ LOCK_REFRESH();
|
|
+ m_wallet->stop_background_sync(epee::wipeable_string(wallet_password));
|
|
+ }
|
|
+ catch (const std::exception &e)
|
|
+ {
|
|
+ LOG_ERROR("Failed to stop background sync: " << e.what());
|
|
+ setStatusError(string(tr("Failed to stop background sync: ")) + e.what());
|
|
+ return false;
|
|
+ }
|
|
+ return true;
|
|
+}
|
|
+
|
|
void WalletImpl::addSubaddressAccount(const std::string& label)
|
|
{
|
|
+ if (checkBackgroundSync("cannot add account"))
|
|
+ return;
|
|
m_wallet->add_subaddress_account(label);
|
|
}
|
|
size_t WalletImpl::numSubaddressAccounts() const
|
|
@@ -1407,10 +1543,14 @@ size_t WalletImpl::numSubaddresses(uint32_t accountIndex) const
|
|
}
|
|
void WalletImpl::addSubaddress(uint32_t accountIndex, const std::string& label)
|
|
{
|
|
+ if (checkBackgroundSync("cannot add subbaddress"))
|
|
+ return;
|
|
m_wallet->add_subaddress(accountIndex, label);
|
|
}
|
|
std::string WalletImpl::getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const
|
|
{
|
|
+ if (checkBackgroundSync("cannot get subbaddress label"))
|
|
+ return "";
|
|
try
|
|
{
|
|
return m_wallet->get_subaddress_label({accountIndex, addressIndex});
|
|
@@ -1424,6 +1564,8 @@ std::string WalletImpl::getSubaddressLabel(uint32_t accountIndex, uint32_t addre
|
|
}
|
|
void WalletImpl::setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label)
|
|
{
|
|
+ if (checkBackgroundSync("cannot set subbaddress label"))
|
|
+ return;
|
|
try
|
|
{
|
|
return m_wallet->set_subaddress_label({accountIndex, addressIndex}, label);
|
|
@@ -1437,12 +1579,16 @@ void WalletImpl::setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex
|
|
|
|
MultisigState WalletImpl::multisig() const {
|
|
MultisigState state;
|
|
+ if (checkBackgroundSync("cannot use multisig"))
|
|
+ return state;
|
|
state.isMultisig = m_wallet->multisig(&state.isReady, &state.threshold, &state.total);
|
|
|
|
return state;
|
|
}
|
|
|
|
string WalletImpl::getMultisigInfo() const {
|
|
+ if (checkBackgroundSync("cannot use multisig"))
|
|
+ return string();
|
|
try {
|
|
clearStatus();
|
|
return m_wallet->get_multisig_first_kex_msg();
|
|
@@ -1455,6 +1601,8 @@ string WalletImpl::getMultisigInfo() const {
|
|
}
|
|
|
|
string WalletImpl::makeMultisig(const vector<string>& info, const uint32_t threshold) {
|
|
+ if (checkBackgroundSync("cannot make multisig"))
|
|
+ return string();
|
|
try {
|
|
clearStatus();
|
|
|
|
@@ -1595,6 +1743,9 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri
|
|
PendingTransactionImpl * transaction = new PendingTransactionImpl(*this);
|
|
|
|
do {
|
|
+ if (checkBackgroundSync("cannot create transactions"))
|
|
+ break;
|
|
+
|
|
std::vector<uint8_t> extra;
|
|
std::string extra_nonce;
|
|
vector<cryptonote::tx_destination_entry> dsts;
|
|
@@ -1761,6 +1912,9 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction()
|
|
PendingTransactionImpl * transaction = new PendingTransactionImpl(*this);
|
|
|
|
do {
|
|
+ if (checkBackgroundSync("cannot sweep"))
|
|
+ break;
|
|
+
|
|
try {
|
|
transaction->m_pending_tx = m_wallet->create_unmixable_sweep_transactions();
|
|
pendingTxPostProcess(transaction);
|
|
@@ -1894,11 +2048,15 @@ uint32_t WalletImpl::defaultMixin() const
|
|
|
|
void WalletImpl::setDefaultMixin(uint32_t arg)
|
|
{
|
|
+ if (checkBackgroundSync("cannot set default mixin"))
|
|
+ return;
|
|
m_wallet->default_mixin(arg);
|
|
}
|
|
|
|
bool WalletImpl::setCacheAttribute(const std::string &key, const std::string &val)
|
|
{
|
|
+ if (checkBackgroundSync("cannot set cache attribute"))
|
|
+ return false;
|
|
m_wallet->set_attribute(key, val);
|
|
return true;
|
|
}
|
|
@@ -1912,6 +2070,8 @@ std::string WalletImpl::getCacheAttribute(const std::string &key) const
|
|
|
|
bool WalletImpl::setUserNote(const std::string &txid, const std::string ¬e)
|
|
{
|
|
+ if (checkBackgroundSync("cannot set user note"))
|
|
+ return false;
|
|
cryptonote::blobdata txid_data;
|
|
if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash))
|
|
return false;
|
|
@@ -1923,6 +2083,8 @@ bool WalletImpl::setUserNote(const std::string &txid, const std::string ¬e)
|
|
|
|
std::string WalletImpl::getUserNote(const std::string &txid) const
|
|
{
|
|
+ if (checkBackgroundSync("cannot get user note"))
|
|
+ return "";
|
|
cryptonote::blobdata txid_data;
|
|
if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash))
|
|
return "";
|
|
@@ -1933,6 +2095,9 @@ std::string WalletImpl::getUserNote(const std::string &txid) const
|
|
|
|
std::string WalletImpl::getTxKey(const std::string &txid_str) const
|
|
{
|
|
+ if (checkBackgroundSync("cannot get tx key"))
|
|
+ return "";
|
|
+
|
|
crypto::hash txid;
|
|
if(!epee::string_tools::hex_to_pod(txid_str, txid))
|
|
{
|
|
@@ -2017,6 +2182,9 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str,
|
|
|
|
std::string WalletImpl::getTxProof(const std::string &txid_str, const std::string &address_str, const std::string &message) const
|
|
{
|
|
+ if (checkBackgroundSync("cannot get tx proof"))
|
|
+ return "";
|
|
+
|
|
crypto::hash txid;
|
|
if (!epee::string_tools::hex_to_pod(txid_str, txid))
|
|
{
|
|
@@ -2073,6 +2241,9 @@ bool WalletImpl::checkTxProof(const std::string &txid_str, const std::string &ad
|
|
}
|
|
|
|
std::string WalletImpl::getSpendProof(const std::string &txid_str, const std::string &message) const {
|
|
+ if (checkBackgroundSync("cannot get spend proof"))
|
|
+ return "";
|
|
+
|
|
crypto::hash txid;
|
|
if(!epee::string_tools::hex_to_pod(txid_str, txid))
|
|
{
|
|
@@ -2115,6 +2286,9 @@ bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string
|
|
}
|
|
|
|
std::string WalletImpl::getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const {
|
|
+ if (checkBackgroundSync("cannot get reserve proof"))
|
|
+ return "";
|
|
+
|
|
try
|
|
{
|
|
clearStatus();
|
|
@@ -2161,6 +2335,9 @@ bool WalletImpl::checkReserveProof(const std::string &address, const std::string
|
|
|
|
std::string WalletImpl::signMessage(const std::string &message, const std::string &address)
|
|
{
|
|
+ if (checkBackgroundSync("cannot sign message"))
|
|
+ return "";
|
|
+
|
|
if (address.empty()) {
|
|
return m_wallet->sign(message, tools::wallet2::sign_with_spend_key);
|
|
}
|
|
@@ -2288,6 +2465,16 @@ bool WalletImpl::isDeterministic() const
|
|
return m_wallet->is_deterministic();
|
|
}
|
|
|
|
+bool WalletImpl::isBackgroundSyncing() const
|
|
+{
|
|
+ return m_wallet->is_background_syncing();
|
|
+}
|
|
+
|
|
+bool WalletImpl::isBackgroundWallet() const
|
|
+{
|
|
+ return m_wallet->is_background_wallet();
|
|
+}
|
|
+
|
|
void WalletImpl::clearStatus() const
|
|
{
|
|
boost::lock_guard<boost::mutex> l(m_statusMutex);
|
|
@@ -2356,9 +2543,7 @@ void WalletImpl::doRefresh()
|
|
if(rescan)
|
|
m_wallet->rescan_blockchain(false);
|
|
m_wallet->refresh(trustedDaemon());
|
|
- if (!m_synchronized) {
|
|
- m_synchronized = true;
|
|
- }
|
|
+ m_synchronized = m_wallet->is_synced();
|
|
// assuming if we have empty history, it wasn't initialized yet
|
|
// for further history changes client need to update history in
|
|
// "on_money_received" and "on_money_sent" callbacks
|
|
@@ -2462,6 +2647,24 @@ bool WalletImpl::doInit(const string &daemon_address, const std::string &proxy_a
|
|
return true;
|
|
}
|
|
|
|
+bool WalletImpl::checkBackgroundSync(const std::string &message) const
|
|
+{
|
|
+ clearStatus();
|
|
+ if (m_wallet->is_background_wallet())
|
|
+ {
|
|
+ LOG_ERROR("Background wallets " + message);
|
|
+ setStatusError(tr("Background wallets ") + message);
|
|
+ return true;
|
|
+ }
|
|
+ if (m_wallet->is_background_syncing())
|
|
+ {
|
|
+ LOG_ERROR(message + " while background syncing");
|
|
+ setStatusError(message + tr(" while background syncing. Stop background syncing first."));
|
|
+ return true;
|
|
+ }
|
|
+ return false;
|
|
+}
|
|
+
|
|
bool WalletImpl::parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error)
|
|
{
|
|
return m_wallet->parse_uri(uri, address, payment_id, amount, tx_description, recipient_name, unknown_parameters, error);
|
|
@@ -2480,6 +2683,8 @@ std::string WalletImpl::getDefaultDataDir() const
|
|
bool WalletImpl::rescanSpent()
|
|
{
|
|
clearStatus();
|
|
+ if (checkBackgroundSync("cannot rescan spent"))
|
|
+ return false;
|
|
if (!trustedDaemon()) {
|
|
setStatusError(tr("Rescan spent can only be used with a trusted daemon"));
|
|
return false;
|
|
diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h
|
|
index 787215ab3..9acd2871f 100644
|
|
--- a/src/wallet/api/wallet.h
|
|
+++ b/src/wallet/api/wallet.h
|
|
@@ -181,6 +181,13 @@ public:
|
|
bool importOutputs(const std::string &filename) override;
|
|
bool scanTransactions(const std::vector<std::string> &txids) override;
|
|
|
|
+ bool setupBackgroundSync(const BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional<std::string> &background_cache_password = optional<std::string>()) override;
|
|
+ BackgroundSyncType getBackgroundSyncType() const override;
|
|
+ bool startBackgroundSync() override;
|
|
+ bool stopBackgroundSync(const std::string &wallet_password) override;
|
|
+ bool isBackgroundSyncing() const override;
|
|
+ bool isBackgroundWallet() const override;
|
|
+
|
|
virtual void disposeTransaction(PendingTransaction * t) override;
|
|
virtual uint64_t estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations,
|
|
PendingTransaction::Priority priority) const override;
|
|
@@ -249,6 +256,7 @@ private:
|
|
bool isNewWallet() const;
|
|
void pendingTxPostProcess(PendingTransactionImpl * pending);
|
|
bool doInit(const std::string &daemon_address, const std::string &proxy_address, uint64_t upper_transaction_size_limit = 0, bool ssl = false);
|
|
+ bool checkBackgroundSync(const std::string &message) const;
|
|
|
|
private:
|
|
friend class PendingTransactionImpl;
|
|
@@ -263,6 +271,10 @@ private:
|
|
mutable boost::mutex m_statusMutex;
|
|
mutable int m_status;
|
|
mutable std::string m_errorString;
|
|
+ // TODO: harden password handling in the wallet API, see relevant discussion
|
|
+ // https://github.com/monero-project/monero-gui/issues/1537
|
|
+ // https://github.com/feather-wallet/feather/issues/72#issuecomment-1405602142
|
|
+ // https://github.com/monero-project/monero/pull/8619#issuecomment-1632951461
|
|
std::string m_password;
|
|
std::unique_ptr<TransactionHistoryImpl> m_history;
|
|
std::unique_ptr<Wallet2CallbackImpl> m_wallet2Callback;
|
|
diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h
|
|
index 9ea753083..4268b656e 100644
|
|
--- a/src/wallet/api/wallet2_api.h
|
|
+++ b/src/wallet/api/wallet2_api.h
|
|
@@ -445,6 +445,12 @@ struct Wallet
|
|
ConnectionStatus_WrongVersion
|
|
};
|
|
|
|
+ enum BackgroundSyncType {
|
|
+ BackgroundSync_Off = 0,
|
|
+ BackgroundSync_ReusePassword = 1,
|
|
+ BackgroundSync_CustomPassword = 2
|
|
+ };
|
|
+
|
|
virtual ~Wallet() = 0;
|
|
virtual std::string seed(const std::string& seed_offset = "") const = 0;
|
|
virtual std::string getSeedLanguage() const = 0;
|
|
@@ -940,6 +946,42 @@ struct Wallet
|
|
*/
|
|
virtual bool scanTransactions(const std::vector<std::string> &txids) = 0;
|
|
|
|
+ /*!
|
|
+ * \brief setupBackgroundSync - setup background sync mode with just a view key
|
|
+ * \param background_sync_type - the mode the wallet background syncs in
|
|
+ * \param wallet_password
|
|
+ * \param background_cache_password - custom password to encrypt background cache, only needed for custom password background sync type
|
|
+ * \return - true on success
|
|
+ */
|
|
+ virtual bool setupBackgroundSync(const BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional<std::string> &background_cache_password) = 0;
|
|
+
|
|
+ /*!
|
|
+ * \brief getBackgroundSyncType - get mode the wallet background syncs in
|
|
+ * \return - the type, or off if type is unknown
|
|
+ */
|
|
+ virtual BackgroundSyncType getBackgroundSyncType() const = 0;
|
|
+
|
|
+ /**
|
|
+ * @brief startBackgroundSync - sync the chain in the background with just view key
|
|
+ */
|
|
+ virtual bool startBackgroundSync() = 0;
|
|
+
|
|
+ /**
|
|
+ * @brief stopBackgroundSync - bring back spend key and process background synced txs
|
|
+ * \param wallet_password
|
|
+ */
|
|
+ virtual bool stopBackgroundSync(const std::string &wallet_password) = 0;
|
|
+
|
|
+ /**
|
|
+ * @brief isBackgroundSyncing - returns true if the wallet is background syncing
|
|
+ */
|
|
+ virtual bool isBackgroundSyncing() const = 0;
|
|
+
|
|
+ /**
|
|
+ * @brief isBackgroundWallet - returns true if the wallet is a background wallet
|
|
+ */
|
|
+ virtual bool isBackgroundWallet() const = 0;
|
|
+
|
|
virtual TransactionHistory * history() = 0;
|
|
virtual AddressBook * addressBook() = 0;
|
|
virtual Subaddress * subaddress() = 0;
|
|
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
|
|
index e4e02c782..618c43cee 100644
|
|
--- a/src/wallet/wallet2.cpp
|
|
+++ b/src/wallet/wallet2.cpp
|
|
@@ -1010,14 +1010,14 @@ uint64_t num_priv_multisig_keys_post_setup(uint64_t threshold, uint64_t total)
|
|
* @param keys_data_key the chacha key that encrypts wallet keys files
|
|
* @return crypto::chacha_key the chacha key that encrypts the wallet cache files
|
|
*/
|
|
-crypto::chacha_key derive_cache_key(const crypto::chacha_key& keys_data_key)
|
|
+crypto::chacha_key derive_cache_key(const crypto::chacha_key& keys_data_key, const unsigned char domain_separator)
|
|
{
|
|
static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key");
|
|
|
|
crypto::chacha_key cache_key;
|
|
epee::mlocked<tools::scrubbed_arr<char, HASH_SIZE+1>> cache_key_data;
|
|
memcpy(cache_key_data.data(), &keys_data_key, HASH_SIZE);
|
|
- cache_key_data[HASH_SIZE] = config::HASH_KEY_WALLET_CACHE;
|
|
+ cache_key_data[HASH_SIZE] = domain_separator;
|
|
cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&) cache_key);
|
|
|
|
return cache_key;
|
|
@@ -1105,7 +1105,7 @@ wallet_keys_unlocker::wallet_keys_unlocker(wallet2 &w, const boost::optional<too
|
|
boost::lock_guard<boost::mutex> lock(lockers_lock);
|
|
if (lockers++ > 0)
|
|
locked = false;
|
|
- if (!locked || w.is_unattended() || w.ask_password() != tools::wallet2::AskPasswordToDecrypt || w.watch_only())
|
|
+ if (!locked || w.is_unattended() || w.ask_password() != tools::wallet2::AskPasswordToDecrypt || w.watch_only() || w.is_background_syncing())
|
|
{
|
|
locked = false;
|
|
return;
|
|
@@ -1222,6 +1222,11 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std
|
|
m_ignore_outputs_above(MONEY_SUPPLY),
|
|
m_ignore_outputs_below(0),
|
|
m_track_uses(false),
|
|
+ m_is_background_wallet(false),
|
|
+ m_background_sync_type(BackgroundSyncOff),
|
|
+ m_background_syncing(false),
|
|
+ m_processing_background_cache(false),
|
|
+ m_custom_background_key(boost::none),
|
|
m_show_wallet_name_when_locked(false),
|
|
m_inactivity_lock_timeout(DEFAULT_INACTIVITY_LOCK_TIMEOUT),
|
|
m_setup_background_mining(BackgroundMiningMaybe),
|
|
@@ -1877,6 +1882,9 @@ bool has_nonrequested_tx_at_height_or_above_requested(uint64_t height, const std
|
|
//----------------------------------------------------------------------------------------------------
|
|
void wallet2::scan_tx(const std::unordered_set<crypto::hash> &txids)
|
|
{
|
|
+ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error,
|
|
+ "cannot scan tx from background wallet");
|
|
+
|
|
// Get the transactions from daemon in batches sorted lowest height to highest
|
|
tx_entry_data txs_to_scan = get_tx_entries(txids);
|
|
if (txs_to_scan.tx_entries.empty())
|
|
@@ -2184,11 +2192,11 @@ void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, cons
|
|
THROW_WALLET_EXCEPTION_IF(i >= tx.vout.size(), error::wallet_internal_error, "Invalid vout index");
|
|
|
|
// if keys are encrypted, ask for password
|
|
- if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only && !m_multisig_rescan_k)
|
|
+ if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only && !m_multisig_rescan_k && !m_background_syncing)
|
|
{
|
|
static critical_section password_lock;
|
|
CRITICAL_REGION_LOCAL(password_lock);
|
|
- if (!m_encrypt_keys_after_refresh)
|
|
+ if (!m_encrypt_keys_after_refresh && !m_processing_background_cache)
|
|
{
|
|
boost::optional<epee::wipeable_string> pwd = m_callback->on_get_password(pool ? "output found in pool" : "output received");
|
|
THROW_WALLET_EXCEPTION_IF(!pwd, error::password_needed, tr("Password is needed to compute key image for incoming monero"));
|
|
@@ -2200,7 +2208,7 @@ void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, cons
|
|
crypto::public_key output_public_key;
|
|
THROW_WALLET_EXCEPTION_IF(!get_output_public_key(tx.vout[i], output_public_key), error::wallet_internal_error, "Failed to get output public key");
|
|
|
|
- if (m_multisig)
|
|
+ if (m_multisig || m_background_syncing/*no spend key*/)
|
|
{
|
|
tx_scan_info.in_ephemeral.pub = output_public_key;
|
|
tx_scan_info.in_ephemeral.sec = crypto::null_skey;
|
|
@@ -2457,6 +2465,14 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
|
|
THROW_WALLET_EXCEPTION_IF(tx.vout.size() != o_indices.size(), error::wallet_internal_error,
|
|
"transactions outputs size=" + std::to_string(tx.vout.size()) +
|
|
" not match with daemon response size=" + std::to_string(o_indices.size()));
|
|
+
|
|
+ // we're going to re-process this receive when background sync is disabled
|
|
+ if (m_background_syncing && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end())
|
|
+ {
|
|
+ size_t bgs_idx = m_background_sync_data.txs.size();
|
|
+ LOG_PRINT_L2("Adding received tx " << txid << " to background sync data (idx=" << bgs_idx << ")");
|
|
+ m_background_sync_data.txs.insert({txid, background_synced_tx{bgs_idx, tx, o_indices, height, ts, double_spend_seen}});
|
|
+ }
|
|
}
|
|
|
|
for(size_t o: outs)
|
|
@@ -2482,7 +2498,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
|
|
td.m_tx = (const cryptonote::transaction_prefix&)tx;
|
|
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_known = !m_watch_only && !m_multisig && !m_background_syncing;
|
|
if (!td.m_key_image_known)
|
|
{
|
|
// we might have cold signed, and have a mapping to key images
|
|
@@ -2672,10 +2688,17 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
|
|
set_spent(it->second, height);
|
|
if (!ignore_callbacks && 0 != m_callback)
|
|
m_callback->on_money_spent(height, txid, tx, amount, tx, td.m_subaddr_index);
|
|
+
|
|
+ if (m_background_syncing && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end())
|
|
+ {
|
|
+ size_t bgs_idx = m_background_sync_data.txs.size();
|
|
+ LOG_PRINT_L2("Adding spent tx " << txid << " to background sync data (idx=" << bgs_idx << ")");
|
|
+ m_background_sync_data.txs.insert({txid, background_synced_tx{bgs_idx, tx, o_indices, height, ts, double_spend_seen}});
|
|
+ }
|
|
}
|
|
}
|
|
|
|
- if (!pool && m_track_uses)
|
|
+ if (!pool && (m_track_uses || (m_background_syncing && it == m_key_images.end())))
|
|
{
|
|
PERF_TIMER(track_uses);
|
|
const uint64_t amount = in_to_key.amount;
|
|
@@ -2689,7 +2712,19 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
|
|
{
|
|
size_t idx = i->second;
|
|
THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error, "Output tracker cache index out of range");
|
|
- m_transfers[idx].m_uses.push_back(std::make_pair(height, txid));
|
|
+
|
|
+ if (m_track_uses)
|
|
+ m_transfers[idx].m_uses.push_back(std::make_pair(height, txid));
|
|
+
|
|
+ // We'll re-process all txs which *might* be spends when we disable
|
|
+ // background sync and retrieve the spend key. We don't know if an
|
|
+ // output is a spend in this tx if we don't know its key image.
|
|
+ if (m_background_syncing && !m_transfers[idx].m_key_image_known && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end())
|
|
+ {
|
|
+ size_t bgs_idx = m_background_sync_data.txs.size();
|
|
+ LOG_PRINT_L2("Adding plausible spent tx " << txid << " to background sync data (idx=" << bgs_idx << ")");
|
|
+ m_background_sync_data.txs.insert({txid, background_synced_tx{bgs_idx, tx, o_indices, height, ts, double_spend_seen}});
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
@@ -2699,7 +2734,16 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
|
|
continue;
|
|
for (uint64_t offset: offsets)
|
|
if (offset == td.m_global_output_index)
|
|
- td.m_uses.push_back(std::make_pair(height, txid));
|
|
+ {
|
|
+ if (m_track_uses)
|
|
+ td.m_uses.push_back(std::make_pair(height, txid));
|
|
+ if (m_background_syncing && !td.m_key_image_known && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end())
|
|
+ {
|
|
+ size_t bgs_idx = m_background_sync_data.txs.size();
|
|
+ LOG_PRINT_L2("Adding plausible spent tx " << txid << " to background sync data (idx=" << bgs_idx << ")");
|
|
+ m_background_sync_data.txs.insert({txid, background_synced_tx{bgs_idx, tx, o_indices, height, ts, double_spend_seen}});
|
|
+ }
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
@@ -3072,8 +3116,8 @@ void wallet2::pull_blocks(bool first, bool try_incremental, uint64_t start_heigh
|
|
req.start_height = start_height;
|
|
req.no_miner_tx = m_refresh_type == RefreshNoCoinbase;
|
|
|
|
- req.requested_info = first ? COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_AND_POOL : COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_ONLY;
|
|
- if (try_incremental)
|
|
+ req.requested_info = (first && !m_background_syncing) ? COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_AND_POOL : COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_ONLY;
|
|
+ if (try_incremental && !m_background_syncing)
|
|
req.pool_info_since = m_pool_info_query_time;
|
|
|
|
{
|
|
@@ -3100,7 +3144,7 @@ void wallet2::pull_blocks(bool first, bool try_incremental, uint64_t start_heigh
|
|
<< ", height " << blocks_start_height + blocks.size() << ", node height " << res.current_height
|
|
<< ", pool info " << static_cast<unsigned int>(res.pool_info_extent));
|
|
|
|
- if (first)
|
|
+ if (first && !m_background_syncing)
|
|
{
|
|
if (res.pool_info_extent != COMMAND_RPC_GET_BLOCKS_FAST::NONE)
|
|
{
|
|
@@ -3612,6 +3656,9 @@ void wallet2::process_unconfirmed_transfer(bool incremental, const crypto::hash
|
|
// incremental update anymore, because with that we might miss some txs altogether.
|
|
void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed, bool try_incremental)
|
|
{
|
|
+ process_txs.clear();
|
|
+ if (m_background_syncing)
|
|
+ return;
|
|
bool updated = false;
|
|
if (m_pool_info_query_time != 0 && try_incremental)
|
|
{
|
|
@@ -4182,6 +4229,8 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
|
|
}
|
|
|
|
m_first_refresh_done = true;
|
|
+ if (m_background_syncing || m_is_background_wallet)
|
|
+ m_background_sync_data.first_refresh_done = true;
|
|
|
|
LOG_PRINT_L1("Refresh done, blocks received: " << blocks_fetched << ", balance (all accounts): " << print_money(balance_all(false)) << ", unlocked: " << print_money(unlocked_balance_all(false)));
|
|
}
|
|
@@ -4267,6 +4316,14 @@ wallet2::detached_blockchain_data wallet2::detach_blockchain(uint64_t height, st
|
|
td.m_uses.pop_back();
|
|
}
|
|
|
|
+ for (auto it = m_background_sync_data.txs.begin(); it != m_background_sync_data.txs.end(); )
|
|
+ {
|
|
+ if(height <= it->second.height)
|
|
+ it = m_background_sync_data.txs.erase(it);
|
|
+ else
|
|
+ ++it;
|
|
+ }
|
|
+
|
|
if (output_tracker_cache)
|
|
output_tracker_cache->clear();
|
|
|
|
@@ -4343,6 +4400,9 @@ void wallet2::handle_reorg(uint64_t height, std::map<std::pair<uint64_t, uint64_
|
|
error::wallet_internal_error, "Daemon claims reorg below last checkpoint");
|
|
detached_blockchain_data dbd = detach_blockchain(height, output_tracker_cache);
|
|
|
|
+ if (m_background_syncing && height < m_background_sync_data.start_height)
|
|
+ m_background_sync_data.start_height = height;
|
|
+
|
|
if (m_callback)
|
|
m_callback->on_reorg(height, dbd.detached_blockchain.size(), dbd.detached_tx_hashes.size());
|
|
}
|
|
@@ -4352,6 +4412,7 @@ bool wallet2::deinit()
|
|
if(m_is_initialized) {
|
|
m_is_initialized = false;
|
|
unlock_keys_file();
|
|
+ unlock_background_keys_file();
|
|
m_account.deinit();
|
|
}
|
|
return true;
|
|
@@ -4378,6 +4439,9 @@ bool wallet2::clear()
|
|
m_device_last_key_image_sync = 0;
|
|
m_pool_info_query_time = 0;
|
|
m_skip_to_height = 0;
|
|
+ m_background_sync_data.first_refresh_done = false;
|
|
+ m_background_sync_data.start_height = 0;
|
|
+ m_background_sync_data.txs.clear();
|
|
return true;
|
|
}
|
|
//----------------------------------------------------------------------------------------------------
|
|
@@ -4396,13 +4460,32 @@ void wallet2::clear_soft(bool keep_key_images)
|
|
m_scanned_pool_txs[1].clear();
|
|
m_pool_info_query_time = 0;
|
|
m_skip_to_height = 0;
|
|
+ m_background_sync_data.first_refresh_done = false;
|
|
+ m_background_sync_data.start_height = 0;
|
|
+ m_background_sync_data.txs.clear();
|
|
|
|
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);
|
|
}
|
|
-
|
|
+//----------------------------------------------------------------------------------------------------
|
|
+void wallet2::clear_user_data()
|
|
+{
|
|
+ for (auto i = m_confirmed_txs.begin(); i != m_confirmed_txs.end(); ++i)
|
|
+ i->second.m_dests.clear();
|
|
+ for (auto i = m_unconfirmed_txs.begin(); i != m_unconfirmed_txs.end(); ++i)
|
|
+ i->second.m_dests.clear();
|
|
+ for (auto i = m_transfers.begin(); i != m_transfers.end(); ++i)
|
|
+ i->m_frozen = false;
|
|
+ m_tx_keys.clear();
|
|
+ m_tx_notes.clear();
|
|
+ m_address_book.clear();
|
|
+ m_subaddress_labels.clear();
|
|
+ m_attributes.clear();
|
|
+ m_account_tags = std::pair<serializable_map<std::string, std::string>, std::vector<std::string>>();
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
/*!
|
|
* \brief Stores wallet information to wallet file.
|
|
* \param keys_file_name Name of wallet file
|
|
@@ -4414,16 +4497,35 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
|
|
{
|
|
boost::optional<wallet2::keys_file_data> keys_file_data = get_keys_file_data(password, watch_only);
|
|
CHECK_AND_ASSERT_MES(keys_file_data != boost::none, false, "failed to generate wallet keys data");
|
|
-
|
|
+ return store_keys_file_data(keys_file_name, keys_file_data.get());
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
+bool wallet2::store_keys(const std::string& keys_file_name, const crypto::chacha_key& key, bool watch_only, bool background_keys_file)
|
|
+{
|
|
+ boost::optional<wallet2::keys_file_data> keys_file_data = get_keys_file_data(key, watch_only, background_keys_file);
|
|
+ CHECK_AND_ASSERT_MES(keys_file_data != boost::none, false, "failed to generate wallet keys data");
|
|
+ return store_keys_file_data(keys_file_name, keys_file_data.get(), background_keys_file);
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
+bool wallet2::store_keys_file_data(const std::string& keys_file_name, wallet2::keys_file_data &keys_file_data, bool background_keys_file)
|
|
+{
|
|
std::string tmp_file_name = keys_file_name + ".new";
|
|
std::string buf;
|
|
- bool r = ::serialization::dump_binary(keys_file_data.get(), buf);
|
|
+ bool r = ::serialization::dump_binary(keys_file_data, buf);
|
|
r = r && save_to_file(tmp_file_name, buf);
|
|
CHECK_AND_ASSERT_MES(r, false, "failed to generate wallet keys file " << tmp_file_name);
|
|
|
|
- unlock_keys_file();
|
|
+ if (!background_keys_file)
|
|
+ unlock_keys_file();
|
|
+ else
|
|
+ unlock_background_keys_file();
|
|
+
|
|
std::error_code e = tools::replace_file(tmp_file_name, keys_file_name);
|
|
- lock_keys_file();
|
|
+
|
|
+ if (!background_keys_file)
|
|
+ lock_keys_file();
|
|
+ else
|
|
+ lock_background_keys_file(keys_file_name);
|
|
|
|
if (e) {
|
|
boost::filesystem::remove(tmp_file_name);
|
|
@@ -4435,26 +4537,27 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
|
|
}
|
|
//----------------------------------------------------------------------------------------------------
|
|
boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee::wipeable_string& password, bool watch_only)
|
|
+{
|
|
+ crypto::chacha_key key;
|
|
+ crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds);
|
|
+ verify_password_with_cached_key(key);
|
|
+ return get_keys_file_data(key, watch_only);
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
+boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const crypto::chacha_key& key, bool watch_only, bool background_keys_file)
|
|
{
|
|
epee::byte_slice account_data;
|
|
std::string multisig_signers;
|
|
std::string multisig_derivations;
|
|
cryptonote::account_base account = m_account;
|
|
|
|
- crypto::chacha_key key;
|
|
- crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds);
|
|
-
|
|
- // We use m_cache_key as a deterministic test to see if given key corresponds to original password
|
|
- const crypto::chacha_key cache_key = derive_cache_key(key);
|
|
- THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password);
|
|
-
|
|
if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
|
|
{
|
|
account.encrypt_viewkey(key);
|
|
account.decrypt_keys(key);
|
|
}
|
|
|
|
- if (watch_only)
|
|
+ if (watch_only || background_keys_file)
|
|
account.forget_spend_key();
|
|
|
|
account.encrypt_keys(key);
|
|
@@ -4589,6 +4692,9 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee:
|
|
value2.SetInt(m_track_uses ? 1 : 0);
|
|
json.AddMember("track_uses", value2, json.GetAllocator());
|
|
|
|
+ value2.SetInt(m_background_sync_type);
|
|
+ json.AddMember("background_sync_type", value2, json.GetAllocator());
|
|
+
|
|
value2.SetInt(m_show_wallet_name_when_locked ? 1 : 0);
|
|
json.AddMember("show_wallet_name_when_locked", value2, json.GetAllocator());
|
|
|
|
@@ -4646,6 +4752,13 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee:
|
|
value2.SetInt(m_enable_multisig ? 1 : 0);
|
|
json.AddMember("enable_multisig", value2, json.GetAllocator());
|
|
|
|
+ if (m_background_sync_type == BackgroundSyncCustomPassword && !background_keys_file && m_custom_background_key)
|
|
+ {
|
|
+ value.SetString(reinterpret_cast<const char*>(m_custom_background_key.get().data()), m_custom_background_key.get().size());
|
|
+ json.AddMember("custom_background_key", value, json.GetAllocator());
|
|
+ }
|
|
+
|
|
+
|
|
value2.SetInt(m_polyseed ? 1 : 0);
|
|
json.AddMember("polyseed", value2, json.GetAllocator());
|
|
|
|
@@ -4675,13 +4788,81 @@ void wallet2::setup_keys(const epee::wipeable_string &password)
|
|
m_account.decrypt_viewkey(key);
|
|
}
|
|
|
|
- m_cache_key = derive_cache_key(key);
|
|
+ m_cache_key = derive_cache_key(key, config::HASH_KEY_WALLET_CACHE);
|
|
|
|
get_ringdb_key();
|
|
}
|
|
//----------------------------------------------------------------------------------------------------
|
|
+void validate_background_cache_password_usage(const tools::wallet2::BackgroundSyncType background_sync_type, const boost::optional<epee::wipeable_string> &background_cache_password, const bool multisig, const bool watch_only, const bool key_on_device)
|
|
+{
|
|
+ THROW_WALLET_EXCEPTION_IF(multisig || watch_only || key_on_device, error::wallet_internal_error, multisig
|
|
+ ? "Background sync not implemented for multisig wallets" : watch_only
|
|
+ ? "Background sync not implemented for view only wallets"
|
|
+ : "Background sync not implemented for HW wallets");
|
|
+
|
|
+ switch (background_sync_type)
|
|
+ {
|
|
+ case tools::wallet2::BackgroundSyncOff:
|
|
+ {
|
|
+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, "background sync is not enabled");
|
|
+ break;
|
|
+ }
|
|
+ case tools::wallet2::BackgroundSyncReusePassword:
|
|
+ {
|
|
+ THROW_WALLET_EXCEPTION_IF(background_cache_password, error::wallet_internal_error,
|
|
+ "unexpected custom background cache password");
|
|
+ break;
|
|
+ }
|
|
+ case tools::wallet2::BackgroundSyncCustomPassword:
|
|
+ {
|
|
+ THROW_WALLET_EXCEPTION_IF(!background_cache_password, error::wallet_internal_error,
|
|
+ "expected custom background cache password");
|
|
+ break;
|
|
+ }
|
|
+ default: THROW_WALLET_EXCEPTION(error::wallet_internal_error, "unknown background sync type");
|
|
+ }
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
+void get_custom_background_key(const epee::wipeable_string &password, crypto::chacha_key &custom_background_key, const uint64_t kdf_rounds)
|
|
+{
|
|
+ crypto::chacha_key key;
|
|
+ crypto::generate_chacha_key(password.data(), password.size(), key, kdf_rounds);
|
|
+ custom_background_key = derive_cache_key(key, config::HASH_KEY_BACKGROUND_KEYS_FILE);
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
+const crypto::chacha_key wallet2::get_cache_key()
|
|
+{
|
|
+ if (m_background_sync_type == BackgroundSyncCustomPassword && m_background_syncing)
|
|
+ {
|
|
+ THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set");
|
|
+ // Domain separate keys used to encrypt background keys file and cache
|
|
+ return derive_cache_key(m_custom_background_key.get(), config::HASH_KEY_BACKGROUND_CACHE);
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ return m_cache_key;
|
|
+ }
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
+void wallet2::verify_password_with_cached_key(const epee::wipeable_string &password)
|
|
+{
|
|
+ crypto::chacha_key key;
|
|
+ crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds);
|
|
+ verify_password_with_cached_key(key);
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
+void wallet2::verify_password_with_cached_key(const crypto::chacha_key &key)
|
|
+{
|
|
+ // We use m_cache_key as a deterministic test to see if given key corresponds to original password
|
|
+ const crypto::chacha_key cache_key = derive_cache_key(key, config::HASH_KEY_WALLET_CACHE);
|
|
+ THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password);
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
void wallet2::change_password(const std::string &filename, const epee::wipeable_string &original_password, const epee::wipeable_string &new_password)
|
|
{
|
|
+ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error,
|
|
+ "cannot change password from background wallet");
|
|
+
|
|
if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
|
|
decrypt_keys(original_password);
|
|
setup_keys(new_password);
|
|
@@ -4740,8 +4921,24 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
|
|
std::string account_data;
|
|
account_data.resize(keys_file_data.account_data.size());
|
|
crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]);
|
|
- if (json.Parse(account_data.c_str()).HasParseError() || !json.IsObject())
|
|
+ const bool try_v0_format = json.Parse(account_data.c_str()).HasParseError() || !json.IsObject();
|
|
+ if (try_v0_format)
|
|
crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]);
|
|
+
|
|
+ // Check if it's a background keys file if both of the above formats fail
|
|
+ {
|
|
+ m_is_background_wallet = false;
|
|
+ m_background_syncing = false;
|
|
+ cryptonote::account_base account_data_check;
|
|
+ if (try_v0_format && !epee::serialization::load_t_from_binary(account_data_check, account_data))
|
|
+ {
|
|
+ get_custom_background_key(password, key, m_kdf_rounds);
|
|
+ crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]);
|
|
+ m_is_background_wallet = !json.Parse(account_data.c_str()).HasParseError() && json.IsObject();
|
|
+ m_background_syncing = m_is_background_wallet; // start a background wallet background syncing
|
|
+ }
|
|
+ }
|
|
+
|
|
// The contents should be JSON if the wallet follows the new format.
|
|
if (json.Parse(account_data.c_str()).HasParseError())
|
|
{
|
|
@@ -4779,6 +4976,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
|
|
m_ignore_outputs_above = MONEY_SUPPLY;
|
|
m_ignore_outputs_below = 0;
|
|
m_track_uses = false;
|
|
+ m_background_sync_type = BackgroundSyncOff;
|
|
m_show_wallet_name_when_locked = false;
|
|
m_inactivity_lock_timeout = DEFAULT_INACTIVITY_LOCK_TIMEOUT;
|
|
m_setup_background_mining = BackgroundMiningMaybe;
|
|
@@ -4796,6 +4994,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
|
|
m_credits_target = 0;
|
|
m_enable_multisig = false;
|
|
m_allow_mismatched_daemon_version = false;
|
|
+ m_custom_background_key = boost::none;
|
|
m_polyseed = false;
|
|
}
|
|
else if(json.IsObject())
|
|
@@ -5034,6 +5233,40 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
|
|
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, enable_multisig, int, Int, false, false);
|
|
m_enable_multisig = field_enable_multisig;
|
|
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, polyseed, int, Int, false, false);
|
|
+
|
|
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, background_sync_type, BackgroundSyncType, Int, false, BackgroundSyncOff);
|
|
+ m_background_sync_type = field_background_sync_type;
|
|
+
|
|
+ // Load encryption key used to encrypt background cache
|
|
+ crypto::chacha_key custom_background_key;
|
|
+ m_custom_background_key = boost::none;
|
|
+ if (m_background_sync_type == BackgroundSyncCustomPassword && !m_is_background_wallet)
|
|
+ {
|
|
+ if (!json.HasMember("custom_background_key"))
|
|
+ {
|
|
+ LOG_ERROR("Field custom_background_key not found in JSON");
|
|
+ return false;
|
|
+ }
|
|
+ else if (!json["custom_background_key"].IsString())
|
|
+ {
|
|
+ LOG_ERROR("Field custom_background_key found in JSON, but not String");
|
|
+ return false;
|
|
+ }
|
|
+ else if (json["custom_background_key"].GetStringLength() != sizeof(crypto::chacha_key))
|
|
+ {
|
|
+ LOG_ERROR("Field custom_background_key found in JSON, but not correct length");
|
|
+ return false;
|
|
+ }
|
|
+ const char *field_custom_background_key = json["custom_background_key"].GetString();
|
|
+ memcpy(custom_background_key.data(), field_custom_background_key, sizeof(crypto::chacha_key));
|
|
+ m_custom_background_key = boost::optional<crypto::chacha_key>(custom_background_key);
|
|
+ LOG_PRINT_L1("Loaded custom background key derived from custom password");
|
|
+ }
|
|
+ else if (json.HasMember("custom_background_key"))
|
|
+ {
|
|
+ LOG_ERROR("Unexpected field custom_background_key found in JSON");
|
|
+ }
|
|
+
|
|
m_polyseed = field_polyseed;
|
|
}
|
|
else
|
|
@@ -5098,12 +5331,17 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
|
|
const cryptonote::account_keys& keys = m_account.get_keys();
|
|
hw::device &hwdev = m_account.get_device();
|
|
r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key);
|
|
- if (!m_watch_only && !m_multisig && hwdev.device_protocol() != hw::device::PROTOCOL_COLD)
|
|
+ if (!m_watch_only && !m_multisig && hwdev.device_protocol() != hw::device::PROTOCOL_COLD && !m_is_background_wallet)
|
|
r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key);
|
|
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file);
|
|
|
|
if (r)
|
|
- setup_keys(password);
|
|
+ {
|
|
+ if (!m_is_background_wallet)
|
|
+ setup_keys(password);
|
|
+ else
|
|
+ m_custom_background_key = boost::optional<crypto::chacha_key>(key);
|
|
+ }
|
|
|
|
return true;
|
|
}
|
|
@@ -5118,11 +5356,12 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
|
|
* can be used prior to rewriting wallet keys file, to ensure user has entered the correct password
|
|
*
|
|
*/
|
|
-bool wallet2::verify_password(const epee::wipeable_string& password)
|
|
+bool wallet2::verify_password(const epee::wipeable_string& password, crypto::secret_key &spend_key_out)
|
|
{
|
|
// this temporary unlocking is necessary for Windows (otherwise the file couldn't be loaded).
|
|
unlock_keys_file();
|
|
- bool r = verify_password(m_keys_file, password, m_account.get_device().device_protocol() == hw::device::PROTOCOL_COLD || m_watch_only || m_multisig, m_account.get_device(), m_kdf_rounds);
|
|
+ const bool no_spend_key = m_account.get_device().device_protocol() == hw::device::PROTOCOL_COLD || m_watch_only || m_multisig || m_is_background_wallet;
|
|
+ bool r = verify_password(m_keys_file, password, no_spend_key, m_account.get_device(), m_kdf_rounds, spend_key_out);
|
|
lock_keys_file();
|
|
return r;
|
|
}
|
|
@@ -5140,7 +5379,7 @@ bool wallet2::verify_password(const epee::wipeable_string& password)
|
|
* can be used prior to rewriting wallet keys file, to ensure user has entered the correct password
|
|
*
|
|
*/
|
|
-bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds)
|
|
+bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds, crypto::secret_key &spend_key_out)
|
|
{
|
|
rapidjson::Document json;
|
|
wallet2::keys_file_data keys_file_data;
|
|
@@ -5157,9 +5396,22 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip
|
|
std::string account_data;
|
|
account_data.resize(keys_file_data.account_data.size());
|
|
crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]);
|
|
- if (json.Parse(account_data.c_str()).HasParseError() || !json.IsObject())
|
|
+ const bool try_v0_format = json.Parse(account_data.c_str()).HasParseError() || !json.IsObject();
|
|
+ if (try_v0_format)
|
|
crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]);
|
|
|
|
+ // Check if it's a background keys file if both of the above formats fail
|
|
+ {
|
|
+ cryptonote::account_base account_data_check;
|
|
+ if (try_v0_format && !epee::serialization::load_t_from_binary(account_data_check, account_data))
|
|
+ {
|
|
+ get_custom_background_key(password, key, kdf_rounds);
|
|
+ crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]);
|
|
+ const bool is_background_wallet = json.Parse(account_data.c_str()).HasParseError() && json.IsObject();
|
|
+ no_spend_key = no_spend_key || is_background_wallet;
|
|
+ }
|
|
+ }
|
|
+
|
|
// The contents should be JSON if the wallet follows the new format.
|
|
if (json.Parse(account_data.c_str()).HasParseError())
|
|
{
|
|
@@ -5184,6 +5436,7 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip
|
|
r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key);
|
|
if(!no_spend_key)
|
|
r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key);
|
|
+ spend_key_out = (!no_spend_key && r) ? keys.m_spend_secret_key : crypto::null_skey;
|
|
return r;
|
|
}
|
|
|
|
@@ -5195,9 +5448,7 @@ void wallet2::encrypt_keys(const crypto::chacha_key &key)
|
|
|
|
void wallet2::decrypt_keys(const crypto::chacha_key &key)
|
|
{
|
|
- // We use m_cache_key as a deterministic test to see if given key corresponds to original password
|
|
- const crypto::chacha_key cache_key = derive_cache_key(key);
|
|
- THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password);
|
|
+ verify_password_with_cached_key(key);
|
|
|
|
m_account.encrypt_viewkey(key);
|
|
m_account.decrypt_keys(key);
|
|
@@ -5915,11 +6166,30 @@ void wallet2::rewrite(const std::string& wallet_name, const epee::wipeable_strin
|
|
{
|
|
if (wallet_name.empty())
|
|
return;
|
|
+ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error,
|
|
+ "cannot change wallet settings from background wallet");
|
|
prepare_file_names(wallet_name);
|
|
boost::system::error_code ignored_ec;
|
|
THROW_WALLET_EXCEPTION_IF(!boost::filesystem::exists(m_keys_file, ignored_ec), error::file_not_found, m_keys_file);
|
|
bool r = store_keys(m_keys_file, password, m_watch_only);
|
|
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
|
|
+
|
|
+ // Update the background keys file when we rewrite the main wallet keys file
|
|
+ if (m_background_sync_type == BackgroundSyncCustomPassword && m_custom_background_key)
|
|
+ {
|
|
+ const std::string background_keys_filename = make_background_keys_file_name(wallet_name);
|
|
+ if (!lock_background_keys_file(background_keys_filename))
|
|
+ {
|
|
+ LOG_ERROR("Background keys file " << background_keys_filename << " is opened by another wallet program and cannot be rewritten");
|
|
+ return; // not fatal, background keys file will just have different wallet settings
|
|
+ }
|
|
+ store_background_keys(m_custom_background_key.get());
|
|
+ store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/);
|
|
+ }
|
|
+ else if (m_background_sync_type == BackgroundSyncReusePassword)
|
|
+ {
|
|
+ reset_background_sync_data(m_background_sync_data);
|
|
+ }
|
|
}
|
|
/*!
|
|
* \brief Writes to a file named based on the normal wallet (doesn't generate key, assumes it's already there)
|
|
@@ -5953,6 +6223,16 @@ bool wallet2::wallet_valid_path_format(const std::string& file_path)
|
|
return !file_path.empty();
|
|
}
|
|
//----------------------------------------------------------------------------------------------------
|
|
+std::string wallet2::make_background_wallet_file_name(const std::string &wallet_file)
|
|
+{
|
|
+ return wallet_file + config::BACKGROUND_WALLET_SUFFIX;
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
+std::string wallet2::make_background_keys_file_name(const std::string &wallet_file)
|
|
+{
|
|
+ return make_background_wallet_file_name(wallet_file) + ".keys";
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
bool wallet2::parse_long_payment_id(const std::string& payment_id_str, crypto::hash& payment_id)
|
|
{
|
|
cryptonote::blobdata payment_id_data;
|
|
@@ -6188,10 +6468,135 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass
|
|
THROW_WALLET_EXCEPTION_IF(true, error::file_read_error, "failed to load keys from buffer");
|
|
}
|
|
|
|
- wallet_keys_unlocker unlocker(*this, m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only, password);
|
|
+ wallet_keys_unlocker unlocker(*this, m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only && !m_is_background_wallet, password);
|
|
|
|
//keys loaded ok!
|
|
//try to load wallet cache. but even if we failed, it is not big problem
|
|
+ load_wallet_cache(use_fs, cache_buf);
|
|
+
|
|
+ if (!m_persistent_rpc_client_id)
|
|
+ set_rpc_client_secret_key(rct::rct2sk(rct::skGen()));
|
|
+
|
|
+ cryptonote::block genesis;
|
|
+ generate_genesis(genesis);
|
|
+ crypto::hash genesis_hash = get_block_hash(genesis);
|
|
+
|
|
+ if (m_blockchain.empty())
|
|
+ {
|
|
+ m_blockchain.push_back(genesis_hash);
|
|
+ m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx);
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ check_genesis(genesis_hash);
|
|
+ }
|
|
+
|
|
+ trim_hashchain();
|
|
+
|
|
+ if (get_num_subaddress_accounts() == 0)
|
|
+ add_subaddress_account(tr("Primary account"));
|
|
+
|
|
+ try
|
|
+ {
|
|
+ find_and_save_rings(false);
|
|
+ }
|
|
+ catch (const std::exception &e)
|
|
+ {
|
|
+ MERROR("Failed to save rings, will try again next time");
|
|
+ }
|
|
+
|
|
+ try
|
|
+ {
|
|
+ if (use_fs)
|
|
+ m_message_store.read_from_file(get_multisig_wallet_state(), m_mms_file, m_load_deprecated_formats);
|
|
+ }
|
|
+ catch (const std::exception &e)
|
|
+ {
|
|
+ MERROR("Failed to initialize MMS, it will be unusable");
|
|
+ }
|
|
+
|
|
+ try
|
|
+ {
|
|
+ if (use_fs)
|
|
+ process_background_cache_on_open();
|
|
+ }
|
|
+ catch (const std::exception &e)
|
|
+ {
|
|
+ MERROR("Failed to process background cache on open: " << e.what());
|
|
+ }
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
+void wallet2::process_background_cache_on_open()
|
|
+{
|
|
+ if (m_wallet_file.empty())
|
|
+ return;
|
|
+ if (m_background_syncing || m_is_background_wallet)
|
|
+ return;
|
|
+ if (m_background_sync_type == BackgroundSyncOff)
|
|
+ return;
|
|
+ if (m_background_sync_type == BackgroundSyncReusePassword)
|
|
+ {
|
|
+ const background_sync_data_t background_sync_data = m_background_sync_data;
|
|
+ const hashchain blockchain = m_blockchain;
|
|
+ process_background_cache(background_sync_data, blockchain, m_last_block_reward);
|
|
+
|
|
+ // Reset the background cache after processing
|
|
+ reset_background_sync_data(m_background_sync_data);
|
|
+ }
|
|
+ else if (m_background_sync_type == BackgroundSyncCustomPassword)
|
|
+ {
|
|
+ // If the background wallet files don't exist, recreate them
|
|
+ const std::string background_keys_file = make_background_keys_file_name(m_wallet_file);
|
|
+ const std::string background_wallet_file = make_background_wallet_file_name(m_wallet_file);
|
|
+ const bool background_keys_file_exists = boost::filesystem::exists(background_keys_file);
|
|
+ const bool background_wallet_exists = boost::filesystem::exists(background_wallet_file);
|
|
+
|
|
+ THROW_WALLET_EXCEPTION_IF(!lock_background_keys_file(background_keys_file), error::background_wallet_already_open, background_wallet_file);
|
|
+ THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set");
|
|
+
|
|
+ if (!background_keys_file_exists)
|
|
+ {
|
|
+ MDEBUG("Background keys file not found, restoring");
|
|
+ store_background_keys(m_custom_background_key.get());
|
|
+ }
|
|
+
|
|
+ if (!background_wallet_exists)
|
|
+ {
|
|
+ MDEBUG("Background cache not found, restoring");
|
|
+ store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ MDEBUG("Loading background cache");
|
|
+
|
|
+ // Set up a minimal background wallet2 instance
|
|
+ std::unique_ptr<wallet2> background_w2(new wallet2(m_nettype));
|
|
+ background_w2->m_is_background_wallet = true;
|
|
+ background_w2->m_background_syncing = true;
|
|
+ background_w2->m_background_sync_type = m_background_sync_type;
|
|
+ background_w2->m_custom_background_key = m_custom_background_key;
|
|
+
|
|
+ cryptonote::account_base account = m_account;
|
|
+ account.forget_spend_key();
|
|
+ background_w2->m_account = account;
|
|
+
|
|
+ // Load background cache from file
|
|
+ background_w2->clear();
|
|
+ background_w2->prepare_file_names(background_wallet_file);
|
|
+ background_w2->load_wallet_cache(true/*use_fs*/);
|
|
+
|
|
+ process_background_cache(background_w2->m_background_sync_data, background_w2->m_blockchain, background_w2->m_last_block_reward);
|
|
+
|
|
+ // Reset the background cache after processing
|
|
+ store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/);
|
|
+ } else {
|
|
+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, "unknown background sync type");
|
|
+ }
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
+void wallet2::load_wallet_cache(const bool use_fs, const std::string& cache_buf)
|
|
+{
|
|
+ boost::system::error_code e;
|
|
bool cache_missing = use_fs ? (!boost::filesystem::exists(m_wallet_file, e) || e) : cache_buf.empty();
|
|
if (cache_missing)
|
|
{
|
|
@@ -6205,7 +6610,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass
|
|
bool r = true;
|
|
if (use_fs)
|
|
{
|
|
- load_from_file(m_wallet_file, cache_file_buf, std::numeric_limits<size_t>::max());
|
|
+ r = load_from_file(m_wallet_file, cache_file_buf, std::numeric_limits<size_t>::max());
|
|
THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, m_wallet_file);
|
|
}
|
|
|
|
@@ -6218,7 +6623,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass
|
|
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + m_wallet_file + '\"');
|
|
std::string cache_data;
|
|
cache_data.resize(cache_file_data.cache_data.size());
|
|
- crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), m_cache_key, cache_file_data.iv, &cache_data[0]);
|
|
+ crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), get_cache_key(), cache_file_data.iv, &cache_data[0]);
|
|
|
|
try {
|
|
bool loaded = false;
|
|
@@ -6459,6 +6864,21 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
|
|
}
|
|
}
|
|
}
|
|
+ else if (m_background_sync_type == BackgroundSyncCustomPassword && m_background_syncing && !m_is_background_wallet)
|
|
+ {
|
|
+ // We're background syncing, so store the wallet cache as a background cache
|
|
+ // keeping the background sync data
|
|
+ try
|
|
+ {
|
|
+ THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set");
|
|
+ store_background_cache(m_custom_background_key.get(), false/*do_reset_background_sync_data*/);
|
|
+ }
|
|
+ catch (const std::exception &e)
|
|
+ {
|
|
+ MERROR("Failed to store background cache while background syncing: " << e.what());
|
|
+ }
|
|
+ return;
|
|
+ }
|
|
|
|
// get wallet cache data
|
|
boost::optional<wallet2::cache_file_data> cache_file_data = get_cache_file_data();
|
|
@@ -6552,6 +6972,22 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
|
|
// store should only exist if the MMS is really active
|
|
m_message_store.write_to_file(get_multisig_wallet_state(), m_mms_file);
|
|
}
|
|
+
|
|
+ if (m_background_sync_type == BackgroundSyncCustomPassword && !m_background_syncing && !m_is_background_wallet)
|
|
+ {
|
|
+ // Update the background wallet cache when we store the main wallet cache
|
|
+ // Note: if background syncing when this is called, it means the background
|
|
+ // wallet is open and was already stored above
|
|
+ try
|
|
+ {
|
|
+ THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set");
|
|
+ store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/);
|
|
+ }
|
|
+ catch (const std::exception &e)
|
|
+ {
|
|
+ MERROR("Failed to update background cache: " << e.what());
|
|
+ }
|
|
+ }
|
|
}
|
|
//----------------------------------------------------------------------------------------------------
|
|
boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data()
|
|
@@ -6569,7 +7005,7 @@ boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data()
|
|
std::string cipher;
|
|
cipher.resize(cache_file_data.get().cache_data.size());
|
|
cache_file_data.get().iv = crypto::rand<crypto::chacha_iv>();
|
|
- crypto::chacha20(cache_file_data.get().cache_data.data(), cache_file_data.get().cache_data.size(), m_cache_key, cache_file_data.get().iv, &cipher[0]);
|
|
+ crypto::chacha20(cache_file_data.get().cache_data.data(), cache_file_data.get().cache_data.size(), get_cache_key(), cache_file_data.get().iv, &cipher[0]);
|
|
cache_file_data.get().cache_data = cipher;
|
|
return cache_file_data;
|
|
}
|
|
@@ -8645,6 +9081,34 @@ bool wallet2::is_keys_file_locked() const
|
|
return m_keys_file_locker->locked();
|
|
}
|
|
|
|
+bool wallet2::lock_background_keys_file(const std::string &background_keys_file)
|
|
+{
|
|
+ if (background_keys_file.empty() || !boost::filesystem::exists(background_keys_file))
|
|
+ return true;
|
|
+ if (m_background_keys_file_locker && m_background_keys_file_locker->locked())
|
|
+ return true;
|
|
+ m_background_keys_file_locker.reset(new tools::file_locker(background_keys_file));
|
|
+ return m_background_keys_file_locker->locked();
|
|
+}
|
|
+
|
|
+bool wallet2::unlock_background_keys_file()
|
|
+{
|
|
+ if (!m_background_keys_file_locker)
|
|
+ {
|
|
+ MDEBUG("background keys file locker is not set");
|
|
+ return false;
|
|
+ }
|
|
+ m_background_keys_file_locker.reset();
|
|
+ return true;
|
|
+}
|
|
+
|
|
+bool wallet2::is_background_keys_file_locked() const
|
|
+{
|
|
+ if (!m_background_keys_file_locker)
|
|
+ return false;
|
|
+ return m_background_keys_file_locker->locked();
|
|
+}
|
|
+
|
|
bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& output_public_key, const rct::key& mask, uint64_t real_index, bool unlocked, std::unordered_set<crypto::public_key> &valid_public_keys_cache) const
|
|
{
|
|
if (!unlocked) // don't add locked outs
|
|
@@ -13980,6 +14444,408 @@ bool wallet2::import_key_images(signed_tx_set & signed_tx, size_t offset, bool o
|
|
return import_key_images(signed_tx.key_images, offset, only_selected_transfers ? boost::make_optional(selected_transfers) : boost::none);
|
|
}
|
|
|
|
+/*
|
|
+ In background sync mode, we use just the view key when the wallet is scanning
|
|
+ to identify all txs where:
|
|
+
|
|
+ 1. We received an output.
|
|
+ 2. We spent an output.
|
|
+ 3. We *may* have spent a received output but we didn't know for sure because
|
|
+ the spend key was not loaded while background sync was enabled.
|
|
+
|
|
+ When the user is ready to use the spend key again, we call this function to
|
|
+ process all those background synced transactions with the spend key loaded,
|
|
+ so that we can properly generate key images for the transactions which we
|
|
+ we were not able to do so for while background sync was enabled. This allows
|
|
+ us to determine *all* receives and spends the user completed while the wallet
|
|
+ had background sync enabled. Once this function completes, we can continue
|
|
+ scanning from where the background sync left off.
|
|
+
|
|
+ Txs of type 3 (txs which we *may* have spent received output(s)) are txs where
|
|
+ 1+ rings contain an output that the user received and the wallet does not know
|
|
+ the associated key image for that output. We don't know if the user spent in
|
|
+ this type of tx or not. This function will generate key images for all outputs
|
|
+ we don't know key images for, and then check if those outputs were spent in
|
|
+ the txs of type 3.
|
|
+
|
|
+ By storing this type of "plausible spend tx" when scanning in background sync
|
|
+ mode, we avoid the need to query the daemon with key images when background
|
|
+ sync mode is disabled to see if those key images were spent. This would
|
|
+ reveal key images to 3rd party nodes for users who don't run their own.
|
|
+ Although this is not a perfect solution to avoid revealing key images to a 3rd
|
|
+ party node (since tx submission trivially reveals key images to a node), it's
|
|
+ probably better than revealing *unused* key images to a 3rd party node, which
|
|
+ would enable the 3rd party to deduce that a tx is spending an output at least
|
|
+ X old when the key image is included in the chain.
|
|
+*/
|
|
+void wallet2::process_background_cache(const background_sync_data_t &background_sync_data, const hashchain &background_synced_chain, uint64_t last_block_reward)
|
|
+{
|
|
+ // We expect the spend key to be in a decrypted state while
|
|
+ // m_processing_background_cache is true
|
|
+ m_processing_background_cache = true;
|
|
+ auto done_processing = epee::misc_utils::create_scope_leave_handler([&, this]() {
|
|
+ m_processing_background_cache = false;
|
|
+ });
|
|
+
|
|
+ if (m_background_syncing || m_multisig || m_watch_only || key_on_device())
|
|
+ return;
|
|
+
|
|
+ if (!background_sync_data.first_refresh_done)
|
|
+ {
|
|
+ MDEBUG("Skipping processing background cache, background cache has not synced yet");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // Skip processing if wallet cache is synced higher than background cache
|
|
+ const uint64_t current_height = m_blockchain.size();
|
|
+ const uint64_t background_height = background_synced_chain.size();
|
|
+ MDEBUG("Background cache height " << background_height << " , wallet height " << current_height);
|
|
+ if (current_height > background_height)
|
|
+ {
|
|
+ MWARNING("Skipping processing background cache, synced height is higher than background cache");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (m_refresh_from_block_height < background_sync_data.wallet_refresh_from_block_height ||
|
|
+ m_subaddress_lookahead_major > background_sync_data.subaddress_lookahead_major ||
|
|
+ m_subaddress_lookahead_minor > background_sync_data.subaddress_lookahead_minor ||
|
|
+ m_refresh_type < background_sync_data.wallet_refresh_type)
|
|
+ {
|
|
+ MWARNING("Skipping processing background cache, background wallet sync settings did not match main wallet's");
|
|
+ MDEBUG("Wallet settings: " <<
|
|
+ ", m_refresh_from_block_height: " << m_refresh_from_block_height << " vs " << background_sync_data.wallet_refresh_from_block_height <<
|
|
+ ", m_subaddress_lookahead_major: " << m_subaddress_lookahead_major << " vs " << background_sync_data.subaddress_lookahead_major <<
|
|
+ ", m_subaddress_lookahead_minor: " << m_subaddress_lookahead_minor << " vs " << background_sync_data.subaddress_lookahead_minor <<
|
|
+ ", m_refresh_type: " << m_refresh_type << " vs " << background_sync_data.wallet_refresh_type);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // Sort background synced txs in the order they appeared in the cache so that
|
|
+ // we process them in the order they appeared in the chain. Thus if tx2 spends
|
|
+ // from tx1, we will know because tx1 is processed before tx2.
|
|
+ std::vector<std::pair<crypto::hash, background_synced_tx>> sorted_bgs_cache(background_sync_data.txs.begin(), background_sync_data.txs.end());
|
|
+ std::sort(sorted_bgs_cache.begin(), sorted_bgs_cache.end(),
|
|
+ [](const std::pair<crypto::hash, background_synced_tx>& l, const std::pair<crypto::hash, background_synced_tx>& r)
|
|
+ {
|
|
+ uint64_t left_index = l.second.index_in_background_sync_data;
|
|
+ uint64_t right_index = r.second.index_in_background_sync_data;
|
|
+ THROW_WALLET_EXCEPTION_IF(
|
|
+ (left_index < right_index && l.second.height > r.second.height) ||
|
|
+ (left_index > right_index && l.second.height < r.second.height),
|
|
+ error::wallet_internal_error, "Unexpected background sync data order");
|
|
+ return left_index < right_index;
|
|
+ });
|
|
+
|
|
+ // All txs in the background cache should have height >= sync start height,
|
|
+ // but not fatal if not
|
|
+ if (!sorted_bgs_cache.empty() && sorted_bgs_cache[0].second.height < background_sync_data.start_height)
|
|
+ MWARNING("First tx in background cache has height (" << sorted_bgs_cache[0].second.height << ") lower than sync start height (" << background_sync_data.start_height << ")");
|
|
+
|
|
+ // We want to process all background synced txs in order to make sure
|
|
+ // the wallet state updates correctly. First we remove all txs from the wallet
|
|
+ // from before the background sync start height, then re-process them in
|
|
+ // chronological order. The background cache should contain a superset of
|
|
+ // *all* the wallet's txs from after the background sync start height.
|
|
+ MDEBUG("Processing " << background_sync_data.txs.size() << " background synced txs starting from height " << background_sync_data.start_height);
|
|
+ detached_blockchain_data dbd = detach_blockchain(background_sync_data.start_height);
|
|
+
|
|
+ for (const auto &bgs_tx : sorted_bgs_cache)
|
|
+ {
|
|
+ MDEBUG("Processing background synced tx " << bgs_tx.first);
|
|
+
|
|
+ process_new_transaction(bgs_tx.first, bgs_tx.second.tx, bgs_tx.second.output_indices, bgs_tx.second.height, 0, bgs_tx.second.block_timestamp,
|
|
+ cryptonote::is_coinbase(bgs_tx.second.tx), false/*pool*/, bgs_tx.second.double_spend_seen, {}, {}, true/*ignore_callbacks*/);
|
|
+
|
|
+ // Re-set destination addresses if they were previously set
|
|
+ if (m_confirmed_txs.find(bgs_tx.first) != m_confirmed_txs.end() &&
|
|
+ dbd.detached_confirmed_txs_dests.find(bgs_tx.first) != dbd.detached_confirmed_txs_dests.end())
|
|
+ {
|
|
+ m_confirmed_txs[bgs_tx.first].m_dests = std::move(dbd.detached_confirmed_txs_dests[bgs_tx.first]);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ m_blockchain = background_synced_chain;
|
|
+ m_last_block_reward = last_block_reward;
|
|
+
|
|
+ MDEBUG("Finished processing background sync data");
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
+void wallet2::reset_background_sync_data(background_sync_data_t &background_sync_data)
|
|
+{
|
|
+ background_sync_data.first_refresh_done = false;
|
|
+ background_sync_data.start_height = get_blockchain_current_height();
|
|
+ background_sync_data.txs.clear();
|
|
+
|
|
+ background_sync_data.wallet_refresh_from_block_height = m_refresh_from_block_height;
|
|
+ background_sync_data.subaddress_lookahead_major = m_subaddress_lookahead_major;
|
|
+ background_sync_data.subaddress_lookahead_minor = m_subaddress_lookahead_minor;
|
|
+ background_sync_data.wallet_refresh_type = m_refresh_type;
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
+void wallet2::store_background_cache(const crypto::chacha_key &custom_background_key, const bool do_reset_background_sync_data)
|
|
+{
|
|
+ MDEBUG("Storing background cache (do_reset_background_sync_data=" << do_reset_background_sync_data << ")");
|
|
+
|
|
+ THROW_WALLET_EXCEPTION_IF(m_background_sync_type != BackgroundSyncCustomPassword, error::wallet_internal_error,
|
|
+ "Can only write a background cache when using a custom background password");
|
|
+
|
|
+ std::unique_ptr<wallet2> background_w2(new wallet2(m_nettype));
|
|
+ background_w2->prepare_file_names(make_background_wallet_file_name(m_wallet_file));
|
|
+
|
|
+ // Make sure background wallet is opened by this wallet
|
|
+ THROW_WALLET_EXCEPTION_IF(!lock_background_keys_file(background_w2->m_keys_file),
|
|
+ error::background_wallet_already_open, background_w2->m_wallet_file);
|
|
+
|
|
+ // Load a background wallet2 instance using this wallet2 instance
|
|
+ std::string this_wallet2;
|
|
+ bool r = ::serialization::dump_binary(*this, this_wallet2);
|
|
+ THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to serialize wallet cache");
|
|
+
|
|
+ background_w2->clear();
|
|
+ r = ::serialization::parse_binary(this_wallet2, *background_w2);
|
|
+ THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to deserialize wallet cache");
|
|
+
|
|
+ // Clear sensitive data from background cache not needed to sync
|
|
+ background_w2->clear_user_data();
|
|
+
|
|
+ background_w2->m_is_background_wallet = true;
|
|
+ if (do_reset_background_sync_data)
|
|
+ reset_background_sync_data(background_w2->m_background_sync_data);
|
|
+ else
|
|
+ background_w2->m_background_sync_data = m_background_sync_data;
|
|
+ background_w2->m_background_syncing = true;
|
|
+
|
|
+ background_w2->m_custom_background_key = boost::optional<crypto::chacha_key>(custom_background_key);
|
|
+ background_w2->m_background_sync_type = m_background_sync_type;
|
|
+ background_w2->store();
|
|
+
|
|
+ MDEBUG("Background cache stored (" << background_w2->m_transfers.size() << " transfers, " << background_w2->m_background_sync_data.txs.size() << " background synced txs)");
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
+void wallet2::store_background_keys(const crypto::chacha_key &custom_background_key)
|
|
+{
|
|
+ MDEBUG("Storing background keys");
|
|
+
|
|
+ const std::string background_keys_file = make_background_keys_file_name(m_wallet_file);
|
|
+ bool r = store_keys(background_keys_file, custom_background_key, false/*watch_only*/, true/*background_keys_file*/);
|
|
+ THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, background_keys_file);
|
|
+ THROW_WALLET_EXCEPTION_IF(!is_background_keys_file_locked(), error::wallet_internal_error, background_keys_file + "\" should be locked");
|
|
+
|
|
+ // GUI uses the address file to differentiate non-mainnet wallets in the UI
|
|
+ const std::string background_address_file = make_background_wallet_file_name(m_wallet_file) + ".address.txt";
|
|
+ if (m_nettype != MAINNET && !boost::filesystem::exists(background_address_file))
|
|
+ {
|
|
+ r = save_to_file(background_address_file, m_account.get_public_address_str(m_nettype), true);
|
|
+ if (!r) MERROR("String with address text not saved");
|
|
+ }
|
|
+
|
|
+ MDEBUG("Background keys stored");
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
+void wallet2::write_background_sync_wallet(const epee::wipeable_string &wallet_password, const epee::wipeable_string &background_cache_password)
|
|
+{
|
|
+ MDEBUG("Storing background sync wallet");
|
|
+ THROW_WALLET_EXCEPTION_IF(m_background_sync_type != BackgroundSyncCustomPassword, error::wallet_internal_error,
|
|
+ "Can only write a background sync wallet when using a custom background password");
|
|
+ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error,
|
|
+ "Can't write background sync wallet from an existing background cache");
|
|
+ THROW_WALLET_EXCEPTION_IF(wallet_password == background_cache_password,
|
|
+ error::background_custom_password_same_as_wallet_password);
|
|
+
|
|
+ // Set the background encryption key
|
|
+ crypto::chacha_key custom_background_key;
|
|
+ get_custom_background_key(background_cache_password, custom_background_key, m_kdf_rounds);
|
|
+
|
|
+ // Keep the background encryption key in memory so the main wallet can update
|
|
+ // the background cache when it stores the main wallet cache
|
|
+ m_custom_background_key = boost::optional<crypto::chacha_key>(custom_background_key);
|
|
+
|
|
+ if (m_wallet_file.empty() || m_keys_file.empty())
|
|
+ return;
|
|
+
|
|
+ // Save background keys file, then background cache, then update main wallet settings
|
|
+ store_background_keys(custom_background_key);
|
|
+ store_background_cache(custom_background_key, true/*do_reset_background_sync_data*/);
|
|
+ bool r = store_keys(m_keys_file, wallet_password, false/*watch_only*/);
|
|
+ THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
|
|
+
|
|
+ MDEBUG("Background sync wallet saved successfully");
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
+void wallet2::setup_background_sync(BackgroundSyncType background_sync_type, const epee::wipeable_string &wallet_password, const boost::optional<epee::wipeable_string> &background_cache_password)
|
|
+{
|
|
+ MDEBUG("Setting background sync to type " << background_sync_type);
|
|
+ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error,
|
|
+ "Can't set background sync type from an existing background cache");
|
|
+ verify_password_with_cached_key(wallet_password);
|
|
+
|
|
+ if (background_sync_type != BackgroundSyncOff)
|
|
+ validate_background_cache_password_usage(background_sync_type, background_cache_password, m_multisig, m_watch_only, key_on_device());
|
|
+
|
|
+ THROW_WALLET_EXCEPTION_IF(background_sync_type == BackgroundSyncCustomPassword && wallet_password == background_cache_password,
|
|
+ error::background_custom_password_same_as_wallet_password);
|
|
+
|
|
+ if (m_background_sync_type == background_sync_type && background_sync_type != BackgroundSyncCustomPassword)
|
|
+ return; // No need to make any changes
|
|
+
|
|
+ if (!m_wallet_file.empty())
|
|
+ {
|
|
+ // Delete existing background files if they already exist
|
|
+ const std::string old_background_wallet_file = make_background_wallet_file_name(m_wallet_file);
|
|
+ const std::string old_background_keys_file = make_background_keys_file_name(m_wallet_file);
|
|
+ const std::string old_background_address_file = old_background_wallet_file + ".address.txt";
|
|
+
|
|
+ // Make sure no other program is using the background wallet
|
|
+ THROW_WALLET_EXCEPTION_IF(!lock_background_keys_file(old_background_keys_file),
|
|
+ error::background_wallet_already_open, old_background_wallet_file);
|
|
+
|
|
+ if (boost::filesystem::exists(old_background_wallet_file))
|
|
+ if (!boost::filesystem::remove(old_background_wallet_file))
|
|
+ LOG_ERROR("Error deleting background wallet file: " << old_background_wallet_file);
|
|
+
|
|
+ if (boost::filesystem::exists(old_background_keys_file))
|
|
+ if (!boost::filesystem::remove(old_background_keys_file))
|
|
+ LOG_ERROR("Error deleting background keys file: " << old_background_keys_file);
|
|
+
|
|
+ if (boost::filesystem::exists(old_background_address_file))
|
|
+ if (!boost::filesystem::remove(old_background_address_file))
|
|
+ LOG_ERROR("Error deleting background address file: " << old_background_address_file);
|
|
+ }
|
|
+
|
|
+ m_background_sync_type = background_sync_type;
|
|
+ m_custom_background_key = boost::none;
|
|
+
|
|
+ // Write the new files
|
|
+ switch (background_sync_type)
|
|
+ {
|
|
+ case BackgroundSyncOff:
|
|
+ case BackgroundSyncReusePassword: rewrite(m_wallet_file, wallet_password); break;
|
|
+ case BackgroundSyncCustomPassword: write_background_sync_wallet(wallet_password, background_cache_password.get()); break;
|
|
+ default: THROW_WALLET_EXCEPTION(error::wallet_internal_error, "unknown background sync type");
|
|
+ }
|
|
+
|
|
+ MDEBUG("Done setting background sync type");
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
+/*
|
|
+ When background syncing, the wallet scans using just the view key, without
|
|
+ keeping the spend key in decrypted state. When a user returns to the wallet
|
|
+ and decrypts the spend key, the wallet processes the background synced txs,
|
|
+ then the wallet picks up scanning normally right where the background sync
|
|
+ left off.
|
|
+*/
|
|
+void wallet2::start_background_sync()
|
|
+{
|
|
+ THROW_WALLET_EXCEPTION_IF(m_background_sync_type == BackgroundSyncOff, error::wallet_internal_error,
|
|
+ "must setup background sync first before using background sync");
|
|
+ THROW_WALLET_EXCEPTION_IF(m_is_background_wallet, error::wallet_internal_error,
|
|
+ "Can't start background syncing from a background wallet (it is always background syncing)");
|
|
+
|
|
+ MDEBUG("Starting background sync");
|
|
+
|
|
+ if (m_background_syncing)
|
|
+ {
|
|
+ MDEBUG("Already background syncing");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (m_background_sync_type == BackgroundSyncCustomPassword && !m_wallet_file.empty())
|
|
+ {
|
|
+ // Save the current state of the wallet cache. Only necessary when using a
|
|
+ // custom background password which uses distinct background wallet to sync.
|
|
+ // When reusing wallet password to sync we reuse the main wallet cache.
|
|
+ store();
|
|
+
|
|
+ // Wipe user data from the background wallet cache not needed to sync.
|
|
+ // Only wipe user data from background cache if wallet cache is stored
|
|
+ // on disk; otherwise we could lose the data.
|
|
+ clear_user_data();
|
|
+
|
|
+ // Wipe m_cache_key since it can be used to decrypt main wallet cache
|
|
+ m_cache_key.scrub();
|
|
+ }
|
|
+
|
|
+ reset_background_sync_data(m_background_sync_data);
|
|
+ m_background_syncing = true;
|
|
+
|
|
+ // Wipe the spend key from memory
|
|
+ m_account.forget_spend_key();
|
|
+
|
|
+ MDEBUG("Background sync started at height " << m_background_sync_data.start_height);
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
+void wallet2::stop_background_sync(const epee::wipeable_string &wallet_password, const crypto::secret_key &spend_secret_key)
|
|
+{
|
|
+ MDEBUG("Stopping background sync");
|
|
+
|
|
+ // Verify provided password and spend secret key. If no spend secret key is
|
|
+ // provided, recover it from the wallet keys file
|
|
+ crypto::secret_key recovered_spend_key = crypto::null_skey;
|
|
+ if (!m_wallet_file.empty())
|
|
+ {
|
|
+ THROW_WALLET_EXCEPTION_IF(!verify_password(wallet_password, recovered_spend_key), error::invalid_password);
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ verify_password_with_cached_key(wallet_password);
|
|
+ }
|
|
+
|
|
+ if (spend_secret_key != crypto::null_skey)
|
|
+ {
|
|
+ THROW_WALLET_EXCEPTION_IF(!m_wallet_file.empty() && spend_secret_key != recovered_spend_key,
|
|
+ error::invalid_spend_key);
|
|
+ MDEBUG("Setting spend secret key with the provided key");
|
|
+ recovered_spend_key = spend_secret_key;
|
|
+ }
|
|
+
|
|
+ // Verify private spend key derives to wallet's public spend key
|
|
+ const auto verify_spend_key = [this](crypto::secret_key &recovered_spend_key) -> bool
|
|
+ {
|
|
+ crypto::public_key spend_public_key;
|
|
+ return recovered_spend_key != crypto::null_skey &&
|
|
+ crypto::secret_key_to_public_key(recovered_spend_key, spend_public_key) &&
|
|
+ m_account.get_keys().m_account_address.m_spend_public_key == spend_public_key;
|
|
+ };
|
|
+ THROW_WALLET_EXCEPTION_IF(!verify_spend_key(recovered_spend_key), error::invalid_spend_key);
|
|
+
|
|
+ THROW_WALLET_EXCEPTION_IF(m_background_sync_type == BackgroundSyncOff, error::wallet_internal_error,
|
|
+ "must setup background sync first before using background sync");
|
|
+ THROW_WALLET_EXCEPTION_IF(m_is_background_wallet, error::wallet_internal_error,
|
|
+ "Can't stop background syncing from a background wallet");
|
|
+
|
|
+ if (!m_background_syncing)
|
|
+ return;
|
|
+
|
|
+ // Copy background cache, we're about to overwrite it
|
|
+ const background_sync_data_t background_sync_data = m_background_sync_data;
|
|
+ const hashchain background_synced_chain = m_blockchain;
|
|
+ const uint64_t last_block_reward = m_last_block_reward;
|
|
+
|
|
+ if (m_background_sync_type == BackgroundSyncCustomPassword && !m_wallet_file.empty())
|
|
+ {
|
|
+ // Reload the wallet from disk
|
|
+ load(m_wallet_file, wallet_password);
|
|
+ THROW_WALLET_EXCEPTION_IF(!verify_spend_key(recovered_spend_key), error::invalid_spend_key);
|
|
+ }
|
|
+ m_background_syncing = false;
|
|
+
|
|
+ // Set the plaintext spend key
|
|
+ m_account.set_spend_key(recovered_spend_key);
|
|
+
|
|
+ // Encrypt the spend key when done if needed
|
|
+ epee::misc_utils::auto_scope_leave_caller keys_reencryptor;
|
|
+ if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
|
|
+ keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]{encrypt_keys(wallet_password);});
|
|
+
|
|
+ // Now we can use the decrypted spend key to process background cache
|
|
+ process_background_cache(background_sync_data, background_synced_chain, last_block_reward);
|
|
+
|
|
+ // Reset the background cache after processing
|
|
+ reset_background_sync_data(m_background_sync_data);
|
|
+
|
|
+ MDEBUG("Background sync stopped");
|
|
+}
|
|
+//----------------------------------------------------------------------------------------------------
|
|
wallet2::payment_container wallet2::export_payments() const
|
|
{
|
|
payment_container payments;
|
|
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
|
|
index b540eff6b..bfe3a4f19 100644
|
|
--- a/src/wallet/wallet2.h
|
|
+++ b/src/wallet/wallet2.h
|
|
@@ -257,6 +257,20 @@ private:
|
|
BackgroundMiningNo = 2,
|
|
};
|
|
|
|
+ enum BackgroundSyncType {
|
|
+ BackgroundSyncOff = 0,
|
|
+ BackgroundSyncReusePassword = 1,
|
|
+ BackgroundSyncCustomPassword = 2,
|
|
+ };
|
|
+
|
|
+ static BackgroundSyncType background_sync_type_from_str(const std::string &background_sync_type_str)
|
|
+ {
|
|
+ if (background_sync_type_str == "off") return BackgroundSyncOff;
|
|
+ if (background_sync_type_str == "reuse-wallet-password") return BackgroundSyncReusePassword;
|
|
+ if (background_sync_type_str == "custom-background-password") return BackgroundSyncCustomPassword;
|
|
+ throw std::logic_error("Unknown background sync type");
|
|
+ };
|
|
+
|
|
enum ExportFormat {
|
|
Binary = 0,
|
|
Ascii,
|
|
@@ -283,7 +297,12 @@ private:
|
|
//! Just parses variables.
|
|
static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, bool unattended, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter);
|
|
|
|
- static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds);
|
|
+ static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds)
|
|
+ {
|
|
+ crypto::secret_key spend_key = crypto::null_skey;
|
|
+ return verify_password(keys_file_name, password, no_spend_key, hwdev, kdf_rounds, spend_key);
|
|
+ };
|
|
+ static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds, crypto::secret_key &spend_key_out);
|
|
static bool query_device(hw::device::device_type& device_type, const std::string& keys_file_name, const epee::wipeable_string& password, uint64_t kdf_rounds = 1);
|
|
|
|
wallet2(cryptonote::network_type nettype = cryptonote::MAINNET, uint64_t kdf_rounds = 1, bool unattended = false, std::unique_ptr<epee::net_utils::http::http_client_factory> http_client_factory = std::unique_ptr<epee::net_utils::http::http_client_factory>(new net::http::client_factory()));
|
|
@@ -793,6 +812,54 @@ private:
|
|
END_SERIALIZE()
|
|
};
|
|
|
|
+ struct background_synced_tx
|
|
+ {
|
|
+ uint64_t index_in_background_sync_data;
|
|
+ cryptonote::transaction tx;
|
|
+ std::vector<uint64_t> output_indices;
|
|
+ uint64_t height;
|
|
+ uint64_t block_timestamp;
|
|
+ bool double_spend_seen;
|
|
+
|
|
+ BEGIN_SERIALIZE_OBJECT()
|
|
+ VERSION_FIELD(0)
|
|
+ VARINT_FIELD(index_in_background_sync_data)
|
|
+
|
|
+ // prune tx; don't need to keep signature data
|
|
+ if (!tx.serialize_base(ar))
|
|
+ return false;
|
|
+
|
|
+ FIELD(output_indices)
|
|
+ VARINT_FIELD(height)
|
|
+ VARINT_FIELD(block_timestamp)
|
|
+ FIELD(double_spend_seen)
|
|
+ END_SERIALIZE()
|
|
+ };
|
|
+
|
|
+ struct background_sync_data_t
|
|
+ {
|
|
+ bool first_refresh_done;
|
|
+ uint64_t start_height;
|
|
+ serializable_unordered_map<crypto::hash, background_synced_tx> txs;
|
|
+
|
|
+ // Relevant wallet settings
|
|
+ uint64_t wallet_refresh_from_block_height;
|
|
+ size_t subaddress_lookahead_major;
|
|
+ size_t subaddress_lookahead_minor;
|
|
+ RefreshType wallet_refresh_type;
|
|
+
|
|
+ BEGIN_SERIALIZE_OBJECT()
|
|
+ VERSION_FIELD(0)
|
|
+ FIELD(first_refresh_done)
|
|
+ FIELD(start_height)
|
|
+ FIELD(txs)
|
|
+ FIELD(wallet_refresh_from_block_height)
|
|
+ VARINT_FIELD(subaddress_lookahead_major)
|
|
+ VARINT_FIELD(subaddress_lookahead_minor)
|
|
+ VARINT_FIELD(wallet_refresh_type)
|
|
+ END_SERIALIZE()
|
|
+ };
|
|
+
|
|
typedef std::tuple<uint64_t, crypto::public_key, rct::key> get_outs_entry;
|
|
|
|
struct parsed_block
|
|
@@ -989,7 +1056,8 @@ private:
|
|
/*!
|
|
* \brief verifies given password is correct for default wallet keys file
|
|
*/
|
|
- bool verify_password(const epee::wipeable_string& password);
|
|
+ bool verify_password(const epee::wipeable_string& password) {crypto::secret_key key = crypto::null_skey; return verify_password(password, key);};
|
|
+ bool verify_password(const epee::wipeable_string& password, crypto::secret_key &spend_key_out);
|
|
cryptonote::account_base& get_account(){return m_account;}
|
|
const cryptonote::account_base& get_account()const{return m_account;}
|
|
|
|
@@ -1093,6 +1161,7 @@ private:
|
|
cryptonote::network_type nettype() const { return m_nettype; }
|
|
bool watch_only() const { return m_watch_only; }
|
|
bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const;
|
|
+ bool is_background_wallet() const { return m_is_background_wallet; }
|
|
bool has_multisig_partial_key_images() const;
|
|
bool has_unknown_key_images() const;
|
|
bool get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase = std::string()) const;
|
|
@@ -1300,6 +1369,11 @@ private:
|
|
return;
|
|
}
|
|
a & m_has_ever_refreshed_from_node;
|
|
+ if ((m_background_sync_type == BackgroundSyncCustomPassword && m_is_background_wallet) ||
|
|
+ (m_background_sync_type == BackgroundSyncReusePassword && !m_is_background_wallet))
|
|
+ {
|
|
+ a & m_background_sync_data;
|
|
+ }
|
|
}
|
|
|
|
BEGIN_SERIALIZE_OBJECT()
|
|
@@ -1336,6 +1410,11 @@ private:
|
|
return true;
|
|
}
|
|
FIELD(m_has_ever_refreshed_from_node)
|
|
+ if ((m_background_sync_type == BackgroundSyncCustomPassword && m_is_background_wallet) ||
|
|
+ (m_background_sync_type == BackgroundSyncReusePassword && !m_is_background_wallet))
|
|
+ {
|
|
+ FIELD(m_background_sync_data)
|
|
+ }
|
|
END_SERIALIZE()
|
|
|
|
/*!
|
|
@@ -1351,6 +1430,8 @@ private:
|
|
* \return Whether path is valid format
|
|
*/
|
|
static bool wallet_valid_path_format(const std::string& file_path);
|
|
+ static std::string make_background_wallet_file_name(const std::string &wallet_file);
|
|
+ static std::string make_background_keys_file_name(const std::string &wallet_file);
|
|
static bool parse_long_payment_id(const std::string& payment_id_str, crypto::hash& payment_id);
|
|
static bool parse_short_payment_id(const std::string& payment_id_str, crypto::hash8& payment_id);
|
|
static bool parse_payment_id(const std::string& payment_id_str, crypto::hash& payment_id);
|
|
@@ -1399,6 +1480,9 @@ private:
|
|
void ignore_outputs_below(uint64_t value) { m_ignore_outputs_below = value; }
|
|
bool track_uses() const { return m_track_uses; }
|
|
void track_uses(bool value) { m_track_uses = value; }
|
|
+ BackgroundSyncType background_sync_type() const { return m_background_sync_type; }
|
|
+ void setup_background_sync(BackgroundSyncType background_sync_type, const epee::wipeable_string &wallet_password, const boost::optional<epee::wipeable_string> &background_cache_password);
|
|
+ bool is_background_syncing() const { return m_background_syncing; }
|
|
bool show_wallet_name_when_locked() const { return m_show_wallet_name_when_locked; }
|
|
void show_wallet_name_when_locked(bool value) { m_show_wallet_name_when_locked = value; }
|
|
BackgroundMiningSetupType setup_background_mining() const { return m_setup_background_mining; }
|
|
@@ -1425,6 +1509,7 @@ private:
|
|
void enable_multisig(bool enable) { m_enable_multisig = enable; }
|
|
bool is_mismatched_daemon_version_allowed() const { return m_allow_mismatched_daemon_version; }
|
|
void allow_mismatched_daemon_version(bool allow_mismatch) { m_allow_mismatched_daemon_version = allow_mismatch; }
|
|
+ uint64_t kdf_rounds() const { return m_kdf_rounds; };
|
|
|
|
bool get_tx_key_cached(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const;
|
|
void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const boost::optional<cryptonote::account_public_address> &single_destination_subaddress = boost::none);
|
|
@@ -1714,6 +1799,9 @@ private:
|
|
uint64_t get_bytes_sent() const;
|
|
uint64_t get_bytes_received() const;
|
|
|
|
+ void start_background_sync();
|
|
+ void stop_background_sync(const epee::wipeable_string &wallet_password, const crypto::secret_key &spend_secret_key = crypto::null_skey);
|
|
+
|
|
// MMS -------------------------------------------------------------------------------------------------
|
|
mms::message_store& get_message_store() { return m_message_store; };
|
|
const mms::message_store& get_message_store() const { return m_message_store; };
|
|
@@ -1749,6 +1837,9 @@ private:
|
|
* \return Whether it was successful.
|
|
*/
|
|
bool store_keys(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only = false);
|
|
+ bool store_keys(const std::string& keys_file_name, const crypto::chacha_key& key, bool watch_only = false, bool background_keys_file = false);
|
|
+ boost::optional<wallet2::keys_file_data> get_keys_file_data(const crypto::chacha_key& key, bool watch_only = false, bool background_keys_file = false);
|
|
+ bool store_keys_file_data(const std::string& keys_file_name, wallet2::keys_file_data &keys_file_data, bool background_keys_file = false);
|
|
/*!
|
|
* \brief Load wallet keys information from wallet file.
|
|
* \param keys_file_name Name of wallet file
|
|
@@ -1762,6 +1853,7 @@ private:
|
|
*/
|
|
bool load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password);
|
|
bool load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password, boost::optional<crypto::chacha_key>& keys_to_encrypt);
|
|
+ void load_wallet_cache(const bool use_fs, const std::string& cache_buf = "");
|
|
void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint8_t block_version, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL, bool ignore_callbacks = false);
|
|
bool should_skip_block(const cryptonote::block &b, uint64_t height) const;
|
|
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, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
|
|
@@ -1770,6 +1862,7 @@ private:
|
|
void get_short_chain_history(std::list<crypto::hash>& ids, uint64_t granularity = 1) const;
|
|
bool clear();
|
|
void clear_soft(bool keep_key_images=false);
|
|
+ void clear_user_data();
|
|
void pull_blocks(bool first, bool try_incremental, 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, uint64_t ¤t_height);
|
|
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);
|
|
void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force = false);
|
|
@@ -1821,10 +1914,23 @@ private:
|
|
bool get_ring(const crypto::chacha_key &key, const crypto::key_image &key_image, std::vector<uint64_t> &outs);
|
|
crypto::chacha_key get_ringdb_key();
|
|
void setup_keys(const epee::wipeable_string &password);
|
|
+ const crypto::chacha_key get_cache_key();
|
|
+ void verify_password_with_cached_key(const epee::wipeable_string &password);
|
|
+ void verify_password_with_cached_key(const crypto::chacha_key &key);
|
|
size_t get_transfer_details(const crypto::key_image &ki) const;
|
|
tx_entry_data get_tx_entries(const std::unordered_set<crypto::hash> &txids);
|
|
void sort_scan_tx_entries(std::vector<process_tx_entry_t> &unsorted_tx_entries);
|
|
void process_scan_txs(const tx_entry_data &txs_to_scan, const tx_entry_data &txs_to_reprocess, const std::unordered_set<crypto::hash> &tx_hashes_to_reprocess, detached_blockchain_data &dbd);
|
|
+ void write_background_sync_wallet(const epee::wipeable_string &wallet_password, const epee::wipeable_string &background_cache_password);
|
|
+ void process_background_cache_on_open();
|
|
+ void process_background_cache(const background_sync_data_t &background_sync_data, const hashchain &background_chain, uint64_t last_block_reward);
|
|
+ void reset_background_sync_data(background_sync_data_t &background_sync_data);
|
|
+ void store_background_cache(const crypto::chacha_key &custom_background_key, const bool do_reset_background_sync_data = true);
|
|
+ void store_background_keys(const crypto::chacha_key &custom_background_key);
|
|
+
|
|
+ bool lock_background_keys_file(const std::string &background_keys_file);
|
|
+ bool unlock_background_keys_file();
|
|
+ bool is_background_keys_file_locked() const;
|
|
|
|
void register_devices();
|
|
hw::device& lookup_device(const std::string & device_descriptor);
|
|
@@ -1940,6 +2046,11 @@ private:
|
|
uint64_t m_ignore_outputs_above;
|
|
uint64_t m_ignore_outputs_below;
|
|
bool m_track_uses;
|
|
+ bool m_is_background_wallet;
|
|
+ BackgroundSyncType m_background_sync_type;
|
|
+ bool m_background_syncing;
|
|
+ bool m_processing_background_cache;
|
|
+ background_sync_data_t m_background_sync_data;
|
|
bool m_show_wallet_name_when_locked;
|
|
uint32_t m_inactivity_lock_timeout;
|
|
BackgroundMiningSetupType m_setup_background_mining;
|
|
@@ -1985,6 +2096,7 @@ private:
|
|
|
|
uint64_t m_last_block_reward;
|
|
std::unique_ptr<tools::file_locker> m_keys_file_locker;
|
|
+ std::unique_ptr<tools::file_locker> m_background_keys_file_locker;
|
|
|
|
mms::message_store m_message_store;
|
|
bool m_original_keys_available;
|
|
@@ -1992,6 +2104,7 @@ private:
|
|
crypto::secret_key m_original_view_secret_key;
|
|
|
|
crypto::chacha_key m_cache_key;
|
|
+ boost::optional<crypto::chacha_key> m_custom_background_key = boost::none;
|
|
std::shared_ptr<wallet_keys_unlocker> m_encrypt_keys_after_refresh;
|
|
|
|
bool m_unattended;
|
|
@@ -2025,6 +2138,8 @@ BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 1)
|
|
BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 4)
|
|
BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3)
|
|
BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 1)
|
|
+BOOST_CLASS_VERSION(tools::wallet2::background_synced_tx, 0)
|
|
+BOOST_CLASS_VERSION(tools::wallet2::background_sync_data_t, 0)
|
|
|
|
namespace boost
|
|
{
|
|
@@ -2523,6 +2638,29 @@ namespace boost
|
|
return;
|
|
a & x.multisig_sigs;
|
|
}
|
|
+
|
|
+ template <class Archive>
|
|
+ inline void serialize(Archive& a, tools::wallet2::background_synced_tx &x, const boost::serialization::version_type ver)
|
|
+ {
|
|
+ a & x.index_in_background_sync_data;
|
|
+ a & x.tx;
|
|
+ a & x.output_indices;
|
|
+ a & x.height;
|
|
+ a & x.block_timestamp;
|
|
+ a & x.double_spend_seen;
|
|
+ }
|
|
+
|
|
+ template <class Archive>
|
|
+ inline void serialize(Archive& a, tools::wallet2::background_sync_data_t &x, const boost::serialization::version_type ver)
|
|
+ {
|
|
+ a & x.first_refresh_done;
|
|
+ a & x.start_height;
|
|
+ a & x.txs.parent();
|
|
+ a & x.wallet_refresh_from_block_height;
|
|
+ a & x.subaddress_lookahead_major;
|
|
+ a & x.subaddress_lookahead_minor;
|
|
+ a & x.wallet_refresh_type;
|
|
+ }
|
|
}
|
|
}
|
|
|
|
diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h
|
|
index 1f7e1c75d..d17b721a9 100644
|
|
--- a/src/wallet/wallet_errors.h
|
|
+++ b/src/wallet/wallet_errors.h
|
|
@@ -63,6 +63,7 @@ namespace tools
|
|
// invalid_password
|
|
// invalid_priority
|
|
// invalid_multisig_seed
|
|
+ // invalid_spend_key
|
|
// refresh_error *
|
|
// acc_outs_lookup_error
|
|
// block_parse_error
|
|
@@ -96,6 +97,9 @@ namespace tools
|
|
// wallet_files_doesnt_correspond
|
|
// scan_tx_error *
|
|
// wont_reprocess_recent_txs_via_untrusted_daemon
|
|
+ // background_sync_error *
|
|
+ // background_wallet_already_open
|
|
+ // background_custom_password_same_as_wallet_password
|
|
//
|
|
// * - class with protected ctor
|
|
|
|
@@ -303,6 +307,16 @@ namespace tools
|
|
std::string to_string() const { return wallet_logic_error::to_string(); }
|
|
};
|
|
|
|
+ struct invalid_spend_key : public wallet_logic_error
|
|
+ {
|
|
+ explicit invalid_spend_key(std::string&& loc)
|
|
+ : wallet_logic_error(std::move(loc), "invalid spend key")
|
|
+ {
|
|
+ }
|
|
+
|
|
+ std::string to_string() const { return wallet_logic_error::to_string(); }
|
|
+ };
|
|
+
|
|
//----------------------------------------------------------------------------------------------------
|
|
struct invalid_pregenerated_random : public wallet_logic_error
|
|
{
|
|
@@ -944,6 +958,31 @@ namespace tools
|
|
}
|
|
};
|
|
//----------------------------------------------------------------------------------------------------
|
|
+ struct background_sync_error : public wallet_logic_error
|
|
+ {
|
|
+ protected:
|
|
+ explicit background_sync_error(std::string&& loc, const std::string& message)
|
|
+ : wallet_logic_error(std::move(loc), message)
|
|
+ {
|
|
+ }
|
|
+ };
|
|
+ //----------------------------------------------------------------------------------------------------
|
|
+ struct background_wallet_already_open : public background_sync_error
|
|
+ {
|
|
+ explicit background_wallet_already_open(std::string&& loc, const std::string& background_wallet_file)
|
|
+ : background_sync_error(std::move(loc), "background wallet " + background_wallet_file + " is already opened by another wallet program")
|
|
+ {
|
|
+ }
|
|
+ };
|
|
+ //----------------------------------------------------------------------------------------------------
|
|
+ struct background_custom_password_same_as_wallet_password : public background_sync_error
|
|
+ {
|
|
+ explicit background_custom_password_same_as_wallet_password(std::string&& loc)
|
|
+ : background_sync_error(std::move(loc), "custom background password must be different than wallet password")
|
|
+ {
|
|
+ }
|
|
+ };
|
|
+ //----------------------------------------------------------------------------------------------------
|
|
|
|
#if !defined(_MSC_VER)
|
|
|
|
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
|
|
index 4e42d64cd..822fc828b 100644
|
|
--- a/src/wallet/wallet_rpc_server.cpp
|
|
+++ b/src/wallet/wallet_rpc_server.cpp
|
|
@@ -73,6 +73,54 @@ using namespace epee;
|
|
} \
|
|
} while(0)
|
|
|
|
+#define CHECK_IF_BACKGROUND_SYNCING() \
|
|
+ do \
|
|
+ { \
|
|
+ if (!m_wallet) { return not_open(er); } \
|
|
+ if (m_wallet->is_background_wallet()) \
|
|
+ { \
|
|
+ er.code = WALLET_RPC_ERROR_CODE_IS_BACKGROUND_WALLET; \
|
|
+ er.message = "This command is disabled for background wallets."; \
|
|
+ return false; \
|
|
+ } \
|
|
+ if (m_wallet->is_background_syncing()) \
|
|
+ { \
|
|
+ er.code = WALLET_RPC_ERROR_CODE_IS_BACKGROUND_SYNCING; \
|
|
+ er.message = "This command is disabled while background syncing. Stop background syncing to use this command."; \
|
|
+ return false; \
|
|
+ } \
|
|
+ } while(0)
|
|
+
|
|
+#define PRE_VALIDATE_BACKGROUND_SYNC() \
|
|
+ do \
|
|
+ { \
|
|
+ if (!m_wallet) { return not_open(er); } \
|
|
+ if (m_restricted) \
|
|
+ { \
|
|
+ er.code = WALLET_RPC_ERROR_CODE_DENIED; \
|
|
+ er.message = "Command unavailable in restricted mode."; \
|
|
+ return false; \
|
|
+ } \
|
|
+ if (m_wallet->key_on_device()) \
|
|
+ { \
|
|
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; \
|
|
+ er.message = "Command not supported by HW wallet"; \
|
|
+ return false; \
|
|
+ } \
|
|
+ if (m_wallet->multisig()) \
|
|
+ { \
|
|
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; \
|
|
+ er.message = "Multisig wallet cannot enable background sync"; \
|
|
+ return false; \
|
|
+ } \
|
|
+ if (m_wallet->watch_only()) \
|
|
+ { \
|
|
+ er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; \
|
|
+ er.message = "Watch-only wallet cannot enable background sync"; \
|
|
+ return false; \
|
|
+ } \
|
|
+ } while (0)
|
|
+
|
|
namespace
|
|
{
|
|
const command_line::arg_descriptor<std::string, true> arg_rpc_bind_port = {"rpc-bind-port", "Sets bind port for server"};
|
|
@@ -291,6 +339,9 @@ namespace tools
|
|
{
|
|
if (!m_wallet)
|
|
return;
|
|
+ // Background mining can be toggled from the main wallet
|
|
+ if (m_wallet->is_background_wallet() || m_wallet->is_background_syncing())
|
|
+ return;
|
|
|
|
tools::wallet2::BackgroundMiningSetupType setup = m_wallet->setup_background_mining();
|
|
if (setup == tools::wallet2::BackgroundMiningNo)
|
|
@@ -582,6 +633,7 @@ namespace tools
|
|
bool wallet_rpc_server::on_create_address(const wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
{
|
|
if (!m_wallet) return not_open(er);
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
try
|
|
{
|
|
if (req.count < 1 || req.count > 64) {
|
|
@@ -619,6 +671,7 @@ namespace tools
|
|
bool wallet_rpc_server::on_label_address(const wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
{
|
|
if (!m_wallet) return not_open(er);
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
try
|
|
{
|
|
m_wallet->set_subaddress_label(req.index, req.label);
|
|
@@ -681,6 +734,7 @@ namespace tools
|
|
bool wallet_rpc_server::on_create_account(const wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
{
|
|
if (!m_wallet) return not_open(er);
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
try
|
|
{
|
|
m_wallet->add_subaddress_account(req.label);
|
|
@@ -698,6 +752,7 @@ namespace tools
|
|
bool wallet_rpc_server::on_label_account(const wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
{
|
|
if (!m_wallet) return not_open(er);
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
try
|
|
{
|
|
m_wallet->set_subaddress_label({req.account_index, 0}, req.label);
|
|
@@ -713,6 +768,7 @@ namespace tools
|
|
bool wallet_rpc_server::on_get_account_tags(const wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
{
|
|
if (!m_wallet) return not_open(er);
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
const std::pair<std::map<std::string, std::string>, std::vector<std::string>> account_tags = m_wallet->get_account_tags();
|
|
for (const std::pair<const std::string, std::string>& p : account_tags.first)
|
|
{
|
|
@@ -732,6 +788,7 @@ namespace tools
|
|
bool wallet_rpc_server::on_tag_accounts(const wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
{
|
|
if (!m_wallet) return not_open(er);
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
try
|
|
{
|
|
m_wallet->set_account_tag(req.accounts, req.tag);
|
|
@@ -747,6 +804,7 @@ namespace tools
|
|
bool wallet_rpc_server::on_untag_accounts(const wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
{
|
|
if (!m_wallet) return not_open(er);
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
try
|
|
{
|
|
m_wallet->set_account_tag(req.accounts, "");
|
|
@@ -762,6 +820,7 @@ namespace tools
|
|
bool wallet_rpc_server::on_set_account_tag_description(const wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::request& req, wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
{
|
|
if (!m_wallet) return not_open(er);
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
try
|
|
{
|
|
m_wallet->set_account_tag_description(req.tag, req.description);
|
|
@@ -792,6 +851,7 @@ namespace tools
|
|
bool wallet_rpc_server::on_freeze(const wallet_rpc::COMMAND_RPC_FREEZE::request& req, wallet_rpc::COMMAND_RPC_FREEZE::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
{
|
|
if (!m_wallet) return not_open(er);
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
try
|
|
{
|
|
if (req.key_image.empty())
|
|
@@ -820,6 +880,7 @@ namespace tools
|
|
bool wallet_rpc_server::on_thaw(const wallet_rpc::COMMAND_RPC_THAW::request& req, wallet_rpc::COMMAND_RPC_THAW::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
{
|
|
if (!m_wallet) return not_open(er);
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
try
|
|
{
|
|
if (req.key_image.empty())
|
|
@@ -848,6 +909,7 @@ namespace tools
|
|
bool wallet_rpc_server::on_frozen(const wallet_rpc::COMMAND_RPC_FROZEN::request& req, wallet_rpc::COMMAND_RPC_FROZEN::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
{
|
|
if (!m_wallet) return not_open(er);
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
try
|
|
{
|
|
if (req.key_image.empty())
|
|
@@ -875,6 +937,8 @@ namespace tools
|
|
//------------------------------------------------------------------------------------------------------------------------------
|
|
bool wallet_rpc_server::validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, bool at_least_one_destination, epee::json_rpc::error& er)
|
|
{
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
+
|
|
crypto::hash8 integrated_payment_id = crypto::null_hash8;
|
|
std::string extra_nonce;
|
|
for (auto it = destinations.begin(); it != destinations.end(); it++)
|
|
@@ -1192,6 +1256,7 @@ namespace tools
|
|
}
|
|
|
|
CHECK_MULTISIG_ENABLED();
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
|
|
cryptonote::blobdata blob;
|
|
if (!epee::string_tools::parse_hexstr_to_binbuff(req.unsigned_txset, blob))
|
|
@@ -1273,6 +1338,7 @@ namespace tools
|
|
er.message = "command not supported by watch-only wallet";
|
|
return false;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
if(req.unsigned_txset.empty() && req.multisig_txset.empty())
|
|
{
|
|
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
|
|
@@ -1542,6 +1608,7 @@ namespace tools
|
|
}
|
|
|
|
CHECK_MULTISIG_ENABLED();
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
|
|
try
|
|
{
|
|
@@ -2091,6 +2158,7 @@ namespace tools
|
|
er.message = "The wallet is watch-only. Cannot retrieve seed.";
|
|
return false;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
if (!m_wallet->is_deterministic())
|
|
{
|
|
er.code = WALLET_RPC_ERROR_CODE_NON_DETERMINISTIC;
|
|
@@ -2119,6 +2187,7 @@ namespace tools
|
|
er.message = "The wallet is watch-only. Cannot retrieve spend key.";
|
|
return false;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
epee::wipeable_string key = epee::to_hex::wipeable_string(m_wallet->get_account().get_keys().m_spend_secret_key);
|
|
res.key = std::string(key.data(), key.size());
|
|
}
|
|
@@ -2140,6 +2209,7 @@ namespace tools
|
|
er.message = "Command unavailable in restricted mode.";
|
|
return false;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
|
|
try
|
|
{
|
|
@@ -2153,6 +2223,79 @@ namespace tools
|
|
return true;
|
|
}
|
|
//------------------------------------------------------------------------------------------------------------------------------
|
|
+ bool wallet_rpc_server::on_setup_background_sync(const wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
+ {
|
|
+ try
|
|
+ {
|
|
+ PRE_VALIDATE_BACKGROUND_SYNC();
|
|
+ const tools::wallet2::BackgroundSyncType background_sync_type = tools::wallet2::background_sync_type_from_str(req.background_sync_type);
|
|
+ boost::optional<epee::wipeable_string> background_cache_password = boost::none;
|
|
+ if (background_sync_type == tools::wallet2::BackgroundSyncCustomPassword)
|
|
+ background_cache_password = boost::optional<epee::wipeable_string>(req.background_cache_password);
|
|
+ m_wallet->setup_background_sync(background_sync_type, req.wallet_password, background_cache_password);
|
|
+ }
|
|
+ catch (...)
|
|
+ {
|
|
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
|
|
+ return false;
|
|
+ }
|
|
+ return true;
|
|
+ }
|
|
+ //------------------------------------------------------------------------------------------------------------------------------
|
|
+ bool wallet_rpc_server::on_start_background_sync(const wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
+ {
|
|
+ try
|
|
+ {
|
|
+ PRE_VALIDATE_BACKGROUND_SYNC();
|
|
+ m_wallet->start_background_sync();
|
|
+ }
|
|
+ catch (...)
|
|
+ {
|
|
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
|
|
+ return false;
|
|
+ }
|
|
+ return true;
|
|
+ }
|
|
+ //------------------------------------------------------------------------------------------------------------------------------
|
|
+ bool wallet_rpc_server::on_stop_background_sync(const wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
+ {
|
|
+ try
|
|
+ {
|
|
+ PRE_VALIDATE_BACKGROUND_SYNC();
|
|
+ crypto::secret_key spend_secret_key = crypto::null_skey;
|
|
+
|
|
+ // Load the spend key from seed if seed is provided
|
|
+ if (!req.seed.empty())
|
|
+ {
|
|
+ crypto::secret_key recovery_key;
|
|
+ std::string language;
|
|
+
|
|
+ if (!crypto::ElectrumWords::words_to_bytes(req.seed, recovery_key, language))
|
|
+ {
|
|
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
|
|
+ er.message = "Electrum-style word list failed verification";
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (!req.seed_offset.empty())
|
|
+ recovery_key = cryptonote::decrypt_key(recovery_key, req.seed_offset);
|
|
+
|
|
+ // generate spend key
|
|
+ cryptonote::account_base account;
|
|
+ account.generate(recovery_key, true, false);
|
|
+ spend_secret_key = account.get_keys().m_spend_secret_key;
|
|
+ }
|
|
+
|
|
+ m_wallet->stop_background_sync(req.wallet_password, spend_secret_key);
|
|
+ }
|
|
+ catch (...)
|
|
+ {
|
|
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
|
|
+ return false;
|
|
+ }
|
|
+ return true;
|
|
+ }
|
|
+ //------------------------------------------------------------------------------------------------------------------------------
|
|
bool wallet_rpc_server::on_sign(const wallet_rpc::COMMAND_RPC_SIGN::request& req, wallet_rpc::COMMAND_RPC_SIGN::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
{
|
|
if (!m_wallet) return not_open(er);
|
|
@@ -2162,6 +2305,7 @@ namespace tools
|
|
er.message = "Command unavailable in restricted mode.";
|
|
return false;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
|
|
tools::wallet2::message_signature_type_t signature_type = tools::wallet2::sign_with_spend_key;
|
|
if (req.signature_type == "spend" || req.signature_type == "")
|
|
@@ -2254,6 +2398,7 @@ namespace tools
|
|
er.message = "Command unavailable in restricted mode.";
|
|
return false;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
|
|
if (req.txids.size() != req.notes.size())
|
|
{
|
|
@@ -2326,6 +2471,7 @@ namespace tools
|
|
er.message = "Command unavailable in restricted mode.";
|
|
return false;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
|
|
m_wallet->set_attribute(req.key, req.value);
|
|
|
|
@@ -2353,6 +2499,7 @@ namespace tools
|
|
bool wallet_rpc_server::on_get_tx_key(const wallet_rpc::COMMAND_RPC_GET_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_GET_TX_KEY::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
{
|
|
if (!m_wallet) return not_open(er);
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
|
|
crypto::hash txid;
|
|
if (!epee::string_tools::hex_to_pod(req.txid, txid))
|
|
@@ -2444,6 +2591,7 @@ namespace tools
|
|
bool wallet_rpc_server::on_get_tx_proof(const wallet_rpc::COMMAND_RPC_GET_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_TX_PROOF::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
{
|
|
if (!m_wallet) return not_open(er);
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
|
|
crypto::hash txid;
|
|
if (!epee::string_tools::hex_to_pod(req.txid, txid))
|
|
@@ -2560,6 +2708,7 @@ namespace tools
|
|
bool wallet_rpc_server::on_get_reserve_proof(const wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
{
|
|
if (!m_wallet) return not_open(er);
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
|
|
boost::optional<std::pair<uint32_t, uint64_t>> account_minreserve;
|
|
if (!req.all)
|
|
@@ -2802,6 +2951,7 @@ namespace tools
|
|
er.message = "command not supported by HW wallet";
|
|
return false;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
|
|
try
|
|
{
|
|
@@ -2831,6 +2981,7 @@ namespace tools
|
|
er.message = "command not supported by HW wallet";
|
|
return false;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
|
|
cryptonote::blobdata blob;
|
|
if (!epee::string_tools::parse_hexstr_to_binbuff(req.outputs_data_hex, blob))
|
|
@@ -2856,6 +3007,7 @@ namespace tools
|
|
bool wallet_rpc_server::on_export_key_images(const wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
{
|
|
if (!m_wallet) return not_open(er);
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
try
|
|
{
|
|
std::pair<uint64_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> ski = m_wallet->export_key_images(req.all);
|
|
@@ -2892,6 +3044,7 @@ namespace tools
|
|
er.message = "This command requires a trusted daemon.";
|
|
return false;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
try
|
|
{
|
|
std::vector<std::pair<crypto::key_image, crypto::signature>> ski;
|
|
@@ -2960,6 +3113,7 @@ namespace tools
|
|
bool wallet_rpc_server::on_get_address_book(const wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
|
{
|
|
if (!m_wallet) return not_open(er);
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
const auto ab = m_wallet->get_address_book();
|
|
if (req.entries.empty())
|
|
{
|
|
@@ -3005,6 +3159,7 @@ namespace tools
|
|
er.message = "Command unavailable in restricted mode.";
|
|
return false;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
|
|
cryptonote::address_parse_info info;
|
|
er.message = "";
|
|
@@ -3047,6 +3202,7 @@ namespace tools
|
|
er.message = "Command unavailable in restricted mode.";
|
|
return false;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
|
|
const auto ab = m_wallet->get_address_book();
|
|
if (req.index >= ab.size())
|
|
@@ -3109,6 +3265,7 @@ namespace tools
|
|
er.message = "Command unavailable in restricted mode.";
|
|
return false;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
|
|
const auto ab = m_wallet->get_address_book();
|
|
if (req.index >= ab.size())
|
|
@@ -3179,6 +3336,7 @@ namespace tools
|
|
er.message = "Command unavailable in restricted mode.";
|
|
return false;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
|
|
std::unordered_set<crypto::hash> txids;
|
|
std::list<std::string>::const_iterator i = req.txids.begin();
|
|
@@ -3218,6 +3376,7 @@ namespace tools
|
|
er.message = "Command unavailable in restricted mode.";
|
|
return false;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
try
|
|
{
|
|
m_wallet->rescan_spent();
|
|
@@ -3482,6 +3641,7 @@ namespace tools
|
|
er.message = "Command unavailable in restricted mode.";
|
|
return false;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
if (m_wallet->verify_password(req.old_password))
|
|
{
|
|
try
|
|
@@ -4009,6 +4169,7 @@ namespace tools
|
|
er.message = "wallet is watch-only and cannot be made multisig";
|
|
return false;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
|
|
res.multisig_info = m_wallet->get_multisig_first_kex_msg();
|
|
return true;
|
|
@@ -4036,6 +4197,7 @@ namespace tools
|
|
er.message = "wallet is watch-only and cannot be made multisig";
|
|
return false;
|
|
}
|
|
+ CHECK_IF_BACKGROUND_SYNCING();
|
|
|
|
try
|
|
{
|
|
diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h
|
|
index 3308d1751..c2329aafe 100644
|
|
--- a/src/wallet/wallet_rpc_server.h
|
|
+++ b/src/wallet/wallet_rpc_server.h
|
|
@@ -160,6 +160,9 @@ namespace tools
|
|
MAP_JON_RPC_WE("set_log_categories", on_set_log_categories, wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES)
|
|
MAP_JON_RPC_WE("estimate_tx_size_and_weight", on_estimate_tx_size_and_weight, wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT)
|
|
MAP_JON_RPC_WE("get_version", on_get_version, wallet_rpc::COMMAND_RPC_GET_VERSION)
|
|
+ MAP_JON_RPC_WE("setup_background_sync", on_setup_background_sync, wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC)
|
|
+ MAP_JON_RPC_WE("start_background_sync", on_start_background_sync, wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC)
|
|
+ MAP_JON_RPC_WE("stop_background_sync", on_stop_background_sync, wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC)
|
|
END_JSON_RPC_MAP()
|
|
END_URI_MAP2()
|
|
|
|
@@ -251,6 +254,9 @@ namespace tools
|
|
bool on_set_log_categories(const wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::request& req, wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
|
|
bool on_estimate_tx_size_and_weight(const wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT::request& req, wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
|
|
bool on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
|
|
+ bool on_setup_background_sync(const wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
|
|
+ bool on_start_background_sync(const wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
|
|
+ bool on_stop_background_sync(const wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
|
|
|
|
//json rpc v2
|
|
bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
|
|
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
|
|
index b6098d95c..a44b56ed6 100644
|
|
--- a/src/wallet/wallet_rpc_server_commands_defs.h
|
|
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
|
|
@@ -2696,5 +2696,69 @@ namespace wallet_rpc
|
|
typedef epee::misc_utils::struct_init<response_t> response;
|
|
};
|
|
|
|
+ struct COMMAND_RPC_SETUP_BACKGROUND_SYNC
|
|
+ {
|
|
+ struct request_t
|
|
+ {
|
|
+ std::string background_sync_type;
|
|
+ std::string wallet_password;
|
|
+ std::string background_cache_password;
|
|
+
|
|
+ BEGIN_KV_SERIALIZE_MAP()
|
|
+ KV_SERIALIZE(background_sync_type)
|
|
+ KV_SERIALIZE(wallet_password)
|
|
+ KV_SERIALIZE_OPT(background_cache_password, (std::string)"")
|
|
+ END_KV_SERIALIZE_MAP()
|
|
+ };
|
|
+ typedef epee::misc_utils::struct_init<request_t> request;
|
|
+
|
|
+ struct response_t
|
|
+ {
|
|
+ BEGIN_KV_SERIALIZE_MAP()
|
|
+ END_KV_SERIALIZE_MAP()
|
|
+ };
|
|
+ typedef epee::misc_utils::struct_init<response_t> response;
|
|
+ };
|
|
+
|
|
+ struct COMMAND_RPC_START_BACKGROUND_SYNC
|
|
+ {
|
|
+ struct request_t
|
|
+ {
|
|
+ BEGIN_KV_SERIALIZE_MAP()
|
|
+ END_KV_SERIALIZE_MAP()
|
|
+ };
|
|
+ typedef epee::misc_utils::struct_init<request_t> request;
|
|
+
|
|
+ struct response_t
|
|
+ {
|
|
+ BEGIN_KV_SERIALIZE_MAP()
|
|
+ END_KV_SERIALIZE_MAP()
|
|
+ };
|
|
+ typedef epee::misc_utils::struct_init<response_t> response;
|
|
+ };
|
|
+
|
|
+ struct COMMAND_RPC_STOP_BACKGROUND_SYNC
|
|
+ {
|
|
+ struct request_t
|
|
+ {
|
|
+ std::string wallet_password;
|
|
+ std::string seed;
|
|
+ std::string seed_offset;
|
|
+
|
|
+ BEGIN_KV_SERIALIZE_MAP()
|
|
+ KV_SERIALIZE(wallet_password)
|
|
+ KV_SERIALIZE_OPT(seed, (std::string)"")
|
|
+ KV_SERIALIZE_OPT(seed_offset, (std::string)"")
|
|
+ END_KV_SERIALIZE_MAP()
|
|
+ };
|
|
+ typedef epee::misc_utils::struct_init<request_t> request;
|
|
+
|
|
+ struct response_t
|
|
+ {
|
|
+ BEGIN_KV_SERIALIZE_MAP()
|
|
+ END_KV_SERIALIZE_MAP()
|
|
+ };
|
|
+ typedef epee::misc_utils::struct_init<response_t> response;
|
|
+ };
|
|
}
|
|
}
|
|
diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h
|
|
index 734229380..b964036bd 100644
|
|
--- a/src/wallet/wallet_rpc_server_error_codes.h
|
|
+++ b/src/wallet/wallet_rpc_server_error_codes.h
|
|
@@ -79,3 +79,5 @@
|
|
#define WALLET_RPC_ERROR_CODE_ZERO_AMOUNT -46
|
|
#define WALLET_RPC_ERROR_CODE_INVALID_SIGNATURE_TYPE -47
|
|
#define WALLET_RPC_ERROR_CODE_DISABLED -48
|
|
+#define WALLET_RPC_ERROR_CODE_IS_BACKGROUND_WALLET -49
|
|
+#define WALLET_RPC_ERROR_CODE_IS_BACKGROUND_SYNCING -50
|
|
diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py
|
|
index 4063911f4..39ee86df3 100755
|
|
--- a/tests/functional_tests/transfer.py
|
|
+++ b/tests/functional_tests/transfer.py
|
|
@@ -30,6 +30,7 @@
|
|
|
|
from __future__ import print_function
|
|
import json
|
|
+import util_resources
|
|
import pprint
|
|
from deepdiff import DeepDiff
|
|
pp = pprint.PrettyPrinter(indent=2)
|
|
@@ -46,6 +47,17 @@ seeds = [
|
|
'dilute gutter certain antics pamphlet macro enjoy left slid guarded bogeys upload nineteen bomb jubilee enhanced irritate turnip eggs swung jukebox loudly reduce sedan slid',
|
|
]
|
|
|
|
+def diff_transfers(actual_transfers, expected_transfers, ignore_order = True):
|
|
+ # The payments containers aren't ordered; re-scanning can lead to diff orders
|
|
+ diff = DeepDiff(actual_transfers, expected_transfers, ignore_order = ignore_order)
|
|
+ if diff != {}:
|
|
+ pp.pprint(diff)
|
|
+ assert diff == {}
|
|
+
|
|
+def diff_incoming_transfers(actual_transfers, expected_transfers):
|
|
+ # wallet2 m_transfers container is ordered and order should be the same across rescans
|
|
+ diff_transfers(actual_transfers, expected_transfers, ignore_order = False)
|
|
+
|
|
class TransferTest():
|
|
def run_test(self):
|
|
self.reset()
|
|
@@ -63,6 +75,8 @@ class TransferTest():
|
|
self.check_is_key_image_spent()
|
|
self.check_scan_tx()
|
|
self.check_subtract_fee_from_outputs()
|
|
+ self.check_background_sync()
|
|
+ self.check_background_sync_reorg_recovery()
|
|
|
|
def reset(self):
|
|
print('Resetting blockchain')
|
|
@@ -71,6 +85,387 @@ class TransferTest():
|
|
daemon.pop_blocks(res.height - 1)
|
|
daemon.flush_txpool()
|
|
|
|
+ def check_background_sync(self):
|
|
+ daemon = Daemon()
|
|
+
|
|
+ print('Testing background sync')
|
|
+
|
|
+ # Background sync type options
|
|
+ REUSE_PASSWORD = 'reuse-wallet-password'
|
|
+ CUSTOM_PASSWORD = 'custom-background-password'
|
|
+
|
|
+ # Some helper functions
|
|
+ def stop_with_wrong_inputs(wallet, wallet_password, seed = ''):
|
|
+ invalid = False
|
|
+ try: wallet.stop_background_sync(wallet_password = wallet_password, seed = seed)
|
|
+ except: invalid = True
|
|
+ assert invalid
|
|
+
|
|
+ def open_with_wrong_password(wallet, filename, password):
|
|
+ invalid_password = False
|
|
+ try: wallet.open_wallet(filename, password = password)
|
|
+ except: invalid_password = True
|
|
+ assert invalid_password
|
|
+
|
|
+ def restore_wallet(wallet, seed, filename = '', password = ''):
|
|
+ wallet.close_wallet()
|
|
+ if filename != '':
|
|
+ util_resources.remove_wallet_files(filename)
|
|
+ wallet.restore_deterministic_wallet(seed = seed, filename = filename, password = password)
|
|
+ wallet.auto_refresh(enable = False)
|
|
+ assert wallet.get_transfers() == {}
|
|
+
|
|
+ def assert_correct_transfers(wallet, expected_transfers, expected_inc_transfers, expected_balance):
|
|
+ diff_transfers(wallet.get_transfers(), expected_transfers)
|
|
+ diff_incoming_transfers(wallet.incoming_transfers(transfer_type = 'all'), expected_inc_transfers)
|
|
+ assert wallet.get_balance().balance == expected_balance
|
|
+
|
|
+ # Set up sender_wallet. Prepare to sweep single output to receiver.
|
|
+ # We're testing a sweep because it makes sure background sync can
|
|
+ # properly pick up txs which do not have a change output back to sender.
|
|
+ sender_wallet = self.wallet[0]
|
|
+ try: sender_wallet.close_wallet()
|
|
+ except: pass
|
|
+ sender_wallet.restore_deterministic_wallet(seed = seeds[0])
|
|
+ sender_wallet.auto_refresh(enable = False)
|
|
+ sender_wallet.refresh()
|
|
+ res = sender_wallet.incoming_transfers(transfer_type = 'available')
|
|
+ unlocked = [x for x in res.transfers if x.unlocked and x.amount > 0]
|
|
+ assert len(unlocked) > 0
|
|
+ ki = unlocked[0].key_image
|
|
+ amount = unlocked[0].amount
|
|
+ spent_txid = unlocked[0].tx_hash
|
|
+ sender_wallet.refresh()
|
|
+ res = sender_wallet.get_transfers()
|
|
+ out_len = 0 if 'out' not in res else len(res.out)
|
|
+ sender_starting_balance = sender_wallet.get_balance().balance
|
|
+
|
|
+ # set up receiver_wallet
|
|
+ receiver_wallet = self.wallet[1]
|
|
+ try: receiver_wallet.close_wallet()
|
|
+ except: pass
|
|
+ receiver_wallet.restore_deterministic_wallet(seed = seeds[1])
|
|
+ receiver_wallet.auto_refresh(enable = False)
|
|
+ receiver_wallet.refresh()
|
|
+ res = receiver_wallet.get_transfers()
|
|
+ in_len = 0 if 'in' not in res else len(res['in'])
|
|
+ receiver_starting_balance = receiver_wallet.get_balance().balance
|
|
+
|
|
+ # transfer from sender_wallet to receiver_wallet
|
|
+ dst = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW'
|
|
+ res = sender_wallet.sweep_single(dst, key_image = ki)
|
|
+ assert len(res.tx_hash) == 32*2
|
|
+ txid = res.tx_hash
|
|
+ assert res.fee > 0
|
|
+ fee = res.fee
|
|
+ assert res.amount == amount - fee
|
|
+
|
|
+ expected_sender_balance = sender_starting_balance - amount
|
|
+ expected_receiver_balance = receiver_starting_balance + (amount - fee)
|
|
+
|
|
+ print('Checking background sync on outgoing wallet')
|
|
+ sender_wallet.setup_background_sync(background_sync_type = REUSE_PASSWORD)
|
|
+ sender_wallet.start_background_sync()
|
|
+ # Mine block to an uninvolved wallet
|
|
+ daemon.generateblocks('46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 1)
|
|
+ # sender should still be able to scan the transfer normally because we
|
|
+ # spent an output that had a known key image
|
|
+ sender_wallet.refresh()
|
|
+ transfers = sender_wallet.get_transfers()
|
|
+ assert 'pending' not in transfers or len(transfers.pending) == 0
|
|
+ assert 'pool' not in transfers or len (transfers.pool) == 0
|
|
+ assert len(transfers.out) == out_len + 1
|
|
+ tx = [x for x in transfers.out if x.txid == txid]
|
|
+ assert len(tx) == 1
|
|
+ tx = tx[0]
|
|
+ assert tx.amount == amount - fee
|
|
+ assert tx.fee == fee
|
|
+ assert len(tx.destinations) == 1
|
|
+ assert tx.destinations[0].amount == amount - fee
|
|
+ assert tx.destinations[0].address == dst
|
|
+ incoming_transfers = sender_wallet.incoming_transfers(transfer_type = 'all')
|
|
+ assert len([x for x in incoming_transfers.transfers if x.tx_hash == spent_txid and x.key_image == ki and x.spent]) == 1
|
|
+ assert sender_wallet.get_balance().balance == expected_sender_balance
|
|
+
|
|
+ # Restore and check background syncing outgoing wallet
|
|
+ restore_wallet(sender_wallet, seeds[0])
|
|
+ sender_wallet.setup_background_sync(background_sync_type = REUSE_PASSWORD)
|
|
+ sender_wallet.start_background_sync()
|
|
+ sender_wallet.refresh()
|
|
+ for i, out_tx in enumerate(transfers.out):
|
|
+ if 'destinations' in out_tx:
|
|
+ del transfers.out[i]['destinations'] # destinations are not expected after wallet restore
|
|
+ # sender's balance should be higher because can't detect spends while
|
|
+ # background sync enabled, only receives
|
|
+ background_bal = sender_wallet.get_balance().balance
|
|
+ assert background_bal > expected_sender_balance
|
|
+ background_transfers = sender_wallet.get_transfers()
|
|
+ assert 'out' not in background_transfers or len(background_transfers.out) == 0
|
|
+ assert 'in' in background_transfers and len(background_transfers['in']) > 0
|
|
+ background_incoming_transfers = sender_wallet.incoming_transfers(transfer_type = 'all')
|
|
+ assert len(background_incoming_transfers) == len(incoming_transfers)
|
|
+ assert len([x for x in background_incoming_transfers.transfers if x.spent or x.key_image != '']) == 0
|
|
+ assert len([x for x in background_incoming_transfers.transfers if x.tx_hash == spent_txid]) == 1
|
|
+
|
|
+ # Try to stop background sync with the wrong seed
|
|
+ stop_with_wrong_inputs(sender_wallet, wallet_password = '', seed = seeds[1])
|
|
+
|
|
+ # Stop background sync and check transfers update correctly
|
|
+ sender_wallet.stop_background_sync(wallet_password = '', seed = seeds[0])
|
|
+ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance)
|
|
+
|
|
+ # Check stopping a wallet with wallet files saved to disk
|
|
+ for background_sync_type in [REUSE_PASSWORD, CUSTOM_PASSWORD]:
|
|
+ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password')
|
|
+ background_cache_password = None if background_sync_type == REUSE_PASSWORD else 'background_password'
|
|
+ sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password)
|
|
+ sender_wallet.start_background_sync()
|
|
+ sender_wallet.refresh()
|
|
+ assert_correct_transfers(sender_wallet, background_transfers, background_incoming_transfers, background_bal)
|
|
+ stop_with_wrong_inputs(sender_wallet, 'wrong_password')
|
|
+ sender_wallet.stop_background_sync(wallet_password = 'test_password')
|
|
+ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance)
|
|
+
|
|
+ # Close wallet while background syncing, then reopen
|
|
+ for background_sync_type in [REUSE_PASSWORD, CUSTOM_PASSWORD]:
|
|
+ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password')
|
|
+ background_cache_password = None if background_sync_type == REUSE_PASSWORD else 'background_password'
|
|
+ sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password)
|
|
+ sender_wallet.start_background_sync()
|
|
+ sender_wallet.refresh()
|
|
+ assert_correct_transfers(sender_wallet, background_transfers, background_incoming_transfers, background_bal)
|
|
+ sender_wallet.close_wallet()
|
|
+ open_with_wrong_password(sender_wallet, 'test1', 'wrong_password')
|
|
+ sender_wallet.open_wallet('test1', password = 'test_password')
|
|
+ # It should reopen with spend key loaded and correctly scan all transfers
|
|
+ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance)
|
|
+
|
|
+ # Close wallet while syncing normally, then reopen
|
|
+ for background_sync_type in [REUSE_PASSWORD, CUSTOM_PASSWORD]:
|
|
+ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password')
|
|
+ background_cache_password = None if background_sync_type == REUSE_PASSWORD else 'background_password'
|
|
+ sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password)
|
|
+ sender_wallet.refresh()
|
|
+ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance)
|
|
+ sender_wallet.close_wallet()
|
|
+ open_with_wrong_password(sender_wallet, 'test1', 'wrong_password')
|
|
+ sender_wallet.open_wallet('test1', password = 'test_password')
|
|
+ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance)
|
|
+
|
|
+ # Create background cache using custom password, then use it to sync, then reopen main wallet
|
|
+ for background_cache_password in ['background_password', '']:
|
|
+ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password')
|
|
+ assert not util_resources.file_exists('test1.background')
|
|
+ assert not util_resources.file_exists('test1.background.keys')
|
|
+ sender_wallet.setup_background_sync(background_sync_type = CUSTOM_PASSWORD, wallet_password = 'test_password', background_cache_password = background_cache_password)
|
|
+ assert util_resources.file_exists('test1.background')
|
|
+ assert util_resources.file_exists('test1.background.keys')
|
|
+ sender_wallet.close_wallet()
|
|
+ open_with_wrong_password(sender_wallet, 'test1.background', 'test_password')
|
|
+ sender_wallet.open_wallet('test1.background', password = background_cache_password)
|
|
+ sender_wallet.refresh()
|
|
+ assert_correct_transfers(sender_wallet, background_transfers, background_incoming_transfers, background_bal)
|
|
+ sender_wallet.close_wallet()
|
|
+ sender_wallet.open_wallet('test1', password = 'test_password')
|
|
+ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance)
|
|
+
|
|
+ # Check that main wallet keeps background cache encrypted with custom password in sync
|
|
+ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password')
|
|
+ sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = 'background_password')
|
|
+ sender_wallet.refresh()
|
|
+ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance)
|
|
+ sender_wallet.close_wallet()
|
|
+ sender_wallet.open_wallet('test1.background', password = 'background_password')
|
|
+ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance)
|
|
+
|
|
+ # Try using wallet password as custom background password
|
|
+ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password')
|
|
+ assert not util_resources.file_exists('test1.background')
|
|
+ assert not util_resources.file_exists('test1.background.keys')
|
|
+ same_password = False
|
|
+ try: sender_wallet.setup_background_sync(background_sync_type = CUSTOM_PASSWORD, wallet_password = 'test_password', background_cache_password = 'test_password')
|
|
+ except: same_password = True
|
|
+ assert same_password
|
|
+ assert not util_resources.file_exists('test1.background')
|
|
+ assert not util_resources.file_exists('test1.background.keys')
|
|
+
|
|
+ # Turn off background sync
|
|
+ for background_sync_type in [REUSE_PASSWORD, CUSTOM_PASSWORD]:
|
|
+ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password')
|
|
+ background_cache_password = None if background_sync_type == REUSE_PASSWORD else 'background_password'
|
|
+ sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password)
|
|
+ if background_sync_type == CUSTOM_PASSWORD:
|
|
+ assert util_resources.file_exists('test1.background')
|
|
+ assert util_resources.file_exists('test1.background.keys')
|
|
+ sender_wallet.close_wallet()
|
|
+ assert util_resources.file_exists('test1.background')
|
|
+ assert util_resources.file_exists('test1.background.keys')
|
|
+ else:
|
|
+ assert not util_resources.file_exists('test1.background')
|
|
+ assert not util_resources.file_exists('test1.background.keys')
|
|
+ sender_wallet.close_wallet()
|
|
+ assert not util_resources.file_exists('test1.background')
|
|
+ assert not util_resources.file_exists('test1.background.keys')
|
|
+ sender_wallet.open_wallet('test1', password = 'test_password')
|
|
+ sender_wallet.setup_background_sync(background_sync_type = 'off', wallet_password = 'test_password')
|
|
+ assert not util_resources.file_exists('test1.background')
|
|
+ assert not util_resources.file_exists('test1.background.keys')
|
|
+ sender_wallet.close_wallet()
|
|
+ assert not util_resources.file_exists('test1.background')
|
|
+ assert not util_resources.file_exists('test1.background.keys')
|
|
+ sender_wallet.open_wallet('test1', password = 'test_password')
|
|
+
|
|
+ # Sanity check against outgoing wallet restored at height 0
|
|
+ sender_wallet.close_wallet()
|
|
+ sender_wallet.restore_deterministic_wallet(seed = seeds[0], restore_height = 0)
|
|
+ sender_wallet.refresh()
|
|
+ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance)
|
|
+
|
|
+ print('Checking background sync on incoming wallet')
|
|
+ receiver_wallet.setup_background_sync(background_sync_type = REUSE_PASSWORD)
|
|
+ receiver_wallet.start_background_sync()
|
|
+ receiver_wallet.refresh()
|
|
+ transfers = receiver_wallet.get_transfers()
|
|
+ assert 'pending' not in transfers or len(transfers.pending) == 0
|
|
+ assert 'pool' not in transfers or len (transfers.pool) == 0
|
|
+ assert len(transfers['in']) == in_len + 1
|
|
+ tx = [x for x in transfers['in'] if x.txid == txid]
|
|
+ assert len(tx) == 1
|
|
+ tx = tx[0]
|
|
+ assert tx.amount == amount - fee
|
|
+ assert tx.fee == fee
|
|
+ incoming_transfers = receiver_wallet.incoming_transfers(transfer_type = 'all')
|
|
+ assert len([x for x in incoming_transfers.transfers if x.tx_hash == txid and x.key_image == '' and not x.spent]) == 1
|
|
+ assert receiver_wallet.get_balance().balance == expected_receiver_balance
|
|
+
|
|
+ # Restore and check background syncing incoming wallet
|
|
+ restore_wallet(receiver_wallet, seeds[1])
|
|
+ receiver_wallet.setup_background_sync(background_sync_type = REUSE_PASSWORD)
|
|
+ receiver_wallet.start_background_sync()
|
|
+ receiver_wallet.refresh()
|
|
+ if 'out' in transfers:
|
|
+ for i, out_tx in enumerate(transfers.out):
|
|
+ if 'destinations' in out_tx:
|
|
+ del transfers.out[i]['destinations'] # destinations are not expected after wallet restore
|
|
+ background_bal = receiver_wallet.get_balance().balance
|
|
+ assert background_bal >= expected_receiver_balance
|
|
+ background_transfers = receiver_wallet.get_transfers()
|
|
+ assert 'out' not in background_transfers or len(background_transfers.out) == 0
|
|
+ assert 'in' in background_transfers and len(background_transfers['in']) > 0
|
|
+ background_incoming_transfers = receiver_wallet.incoming_transfers(transfer_type = 'all')
|
|
+ assert len(background_incoming_transfers) == len(incoming_transfers)
|
|
+ assert len([x for x in background_incoming_transfers.transfers if x.spent or x.key_image != '']) == 0
|
|
+ assert len([x for x in background_incoming_transfers.transfers if x.tx_hash == txid]) == 1
|
|
+
|
|
+ # Stop background sync and check transfers update correctly
|
|
+ receiver_wallet.stop_background_sync(wallet_password = '', seed = seeds[1])
|
|
+ diff_transfers(receiver_wallet.get_transfers(), transfers)
|
|
+ incoming_transfers = receiver_wallet.incoming_transfers(transfer_type = 'all')
|
|
+ assert len(background_incoming_transfers) == len(incoming_transfers)
|
|
+ assert len([x for x in incoming_transfers.transfers if x.tx_hash == txid and x.key_image != '' and not x.spent]) == 1
|
|
+ assert receiver_wallet.get_balance().balance == expected_receiver_balance
|
|
+
|
|
+ # Check a fresh incoming wallet with wallet files saved to disk and encrypted with password
|
|
+ restore_wallet(receiver_wallet, seeds[1], 'test2', 'test_password')
|
|
+ receiver_wallet.setup_background_sync(background_sync_type = REUSE_PASSWORD, wallet_password = 'test_password')
|
|
+ receiver_wallet.start_background_sync()
|
|
+ receiver_wallet.refresh()
|
|
+ assert_correct_transfers(receiver_wallet, background_transfers, background_incoming_transfers, background_bal)
|
|
+ stop_with_wrong_inputs(receiver_wallet, 'wrong_password')
|
|
+ receiver_wallet.stop_background_sync(wallet_password = 'test_password')
|
|
+ assert_correct_transfers(receiver_wallet, transfers, incoming_transfers, expected_receiver_balance)
|
|
+
|
|
+ # Close receiver's wallet while background sync is enabled then reopen
|
|
+ restore_wallet(receiver_wallet, seeds[1], 'test2', 'test_password')
|
|
+ receiver_wallet.setup_background_sync(background_sync_type = REUSE_PASSWORD, wallet_password = 'test_password')
|
|
+ receiver_wallet.start_background_sync()
|
|
+ receiver_wallet.refresh()
|
|
+ diff_transfers(receiver_wallet.get_transfers(), background_transfers)
|
|
+ diff_incoming_transfers(receiver_wallet.incoming_transfers(transfer_type = 'all'), background_incoming_transfers)
|
|
+ assert receiver_wallet.get_balance().balance == background_bal
|
|
+ receiver_wallet.close_wallet()
|
|
+ receiver_wallet.open_wallet('test2', password = 'test_password')
|
|
+ # It should reopen with spend key loaded and correctly scan all transfers
|
|
+ assert_correct_transfers(receiver_wallet, transfers, incoming_transfers, expected_receiver_balance)
|
|
+
|
|
+ # Sanity check against incoming wallet restored at height 0
|
|
+ receiver_wallet.close_wallet()
|
|
+ receiver_wallet.restore_deterministic_wallet(seed = seeds[1], restore_height = 0)
|
|
+ receiver_wallet.refresh()
|
|
+ assert_correct_transfers(receiver_wallet, transfers, incoming_transfers, expected_receiver_balance)
|
|
+
|
|
+ # Clean up
|
|
+ util_resources.remove_wallet_files('test1')
|
|
+ util_resources.remove_wallet_files('test2')
|
|
+ for i in range(2):
|
|
+ self.wallet[i].close_wallet()
|
|
+ self.wallet[i].restore_deterministic_wallet(seed = seeds[i])
|
|
+
|
|
+ def check_background_sync_reorg_recovery(self):
|
|
+ daemon = Daemon()
|
|
+
|
|
+ print('Testing background sync reorg recovery')
|
|
+
|
|
+ # Disconnect daemon from peers
|
|
+ daemon.out_peers(0)
|
|
+
|
|
+ # Background sync type options
|
|
+ REUSE_PASSWORD = 'reuse-wallet-password'
|
|
+ CUSTOM_PASSWORD = 'custom-background-password'
|
|
+
|
|
+ for background_sync_type in [REUSE_PASSWORD, CUSTOM_PASSWORD]:
|
|
+ # Set up wallet saved to disk
|
|
+ sender_wallet = self.wallet[0]
|
|
+ sender_wallet.close_wallet()
|
|
+ util_resources.remove_wallet_files('test1')
|
|
+ sender_wallet.restore_deterministic_wallet(seed = seeds[0], filename = 'test1', password = '')
|
|
+ sender_wallet.auto_refresh(enable = False)
|
|
+ sender_wallet.refresh()
|
|
+ sender_starting_balance = sender_wallet.get_balance().balance
|
|
+
|
|
+ # Send tx and mine a block
|
|
+ amount = 1000000000000
|
|
+ assert sender_starting_balance > amount
|
|
+ dst = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': amount}
|
|
+ res = sender_wallet.transfer([dst])
|
|
+ assert len(res.tx_hash) == 32*2
|
|
+ txid = res.tx_hash
|
|
+
|
|
+ daemon.generateblocks('46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 1)
|
|
+
|
|
+ # Make sure the wallet can see the tx
|
|
+ sender_wallet.refresh()
|
|
+ transfers = sender_wallet.get_transfers()
|
|
+ assert 'pool' not in transfers or len (transfers.pool) == 0
|
|
+ tx = [x for x in transfers.out if x.txid == txid]
|
|
+ assert len(tx) == 1
|
|
+ tx = tx[0]
|
|
+ assert sender_wallet.get_balance().balance < (sender_starting_balance - amount)
|
|
+
|
|
+ # Pop the block while background syncing
|
|
+ background_cache_password = None if background_sync_type == REUSE_PASSWORD else 'background_password'
|
|
+ sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = '', background_cache_password = background_cache_password)
|
|
+ sender_wallet.start_background_sync()
|
|
+ daemon.pop_blocks(1)
|
|
+ daemon.flush_txpool()
|
|
+
|
|
+ daemon.generateblocks('46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 1)
|
|
+
|
|
+ # Make sure the wallet can no longer see the tx
|
|
+ sender_wallet.refresh()
|
|
+ sender_wallet.stop_background_sync(wallet_password = '', seed = seeds[0])
|
|
+ transfers = sender_wallet.get_transfers()
|
|
+ no_tx = [x for x in transfers.out if x.txid == txid]
|
|
+ assert len(no_tx) == 0
|
|
+ assert sender_wallet.get_balance().balance == sender_starting_balance
|
|
+
|
|
+ # Clean up
|
|
+ daemon.out_peers(12)
|
|
+ util_resources.remove_wallet_files('test1')
|
|
+ self.wallet[0].close_wallet()
|
|
+ self.wallet[0].restore_deterministic_wallet(seed = seeds[0])
|
|
+
|
|
+
|
|
def create(self):
|
|
print('Creating wallets')
|
|
self.wallet = [None] * len(seeds)
|
|
@@ -840,12 +1235,6 @@ class TransferTest():
|
|
|
|
print('Testing scan_tx')
|
|
|
|
- def diff_transfers(actual_transfers, expected_transfers):
|
|
- diff = DeepDiff(actual_transfers, expected_transfers)
|
|
- if diff != {}:
|
|
- pp.pprint(diff)
|
|
- assert diff == {}
|
|
-
|
|
# set up sender_wallet
|
|
sender_wallet = self.wallet[0]
|
|
try: sender_wallet.close_wallet()
|
|
diff --git a/tests/functional_tests/util_resources.py b/tests/functional_tests/util_resources.py
|
|
index e030312da..3197187f4 100755
|
|
--- a/tests/functional_tests/util_resources.py
|
|
+++ b/tests/functional_tests/util_resources.py
|
|
@@ -37,6 +37,8 @@
|
|
from __future__ import print_function
|
|
import subprocess
|
|
import psutil
|
|
+import os
|
|
+import errno
|
|
|
|
def available_ram_gb():
|
|
ram_bytes = psutil.virtual_memory().available
|
|
@@ -51,3 +53,26 @@ def get_time_pi_seconds(cores, app_dir='.'):
|
|
miliseconds = int(decoded)
|
|
|
|
return miliseconds / 1000.0
|
|
+
|
|
+def remove_file(name):
|
|
+ WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY']
|
|
+ assert WALLET_DIRECTORY != ''
|
|
+ try:
|
|
+ os.unlink(WALLET_DIRECTORY + '/' + name)
|
|
+ except OSError as e:
|
|
+ if e.errno != errno.ENOENT:
|
|
+ raise
|
|
+
|
|
+def get_file_path(name):
|
|
+ WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY']
|
|
+ assert WALLET_DIRECTORY != ''
|
|
+ return WALLET_DIRECTORY + '/' + name
|
|
+
|
|
+def remove_wallet_files(name):
|
|
+ for suffix in ['', '.keys', '.background', '.background.keys']:
|
|
+ remove_file(name + suffix)
|
|
+
|
|
+def file_exists(name):
|
|
+ WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY']
|
|
+ assert WALLET_DIRECTORY != ''
|
|
+ return os.path.isfile(WALLET_DIRECTORY + '/' + name)
|
|
diff --git a/tests/functional_tests/wallet.py b/tests/functional_tests/wallet.py
|
|
index 1ad05c98f..8182cecb2 100755
|
|
--- a/tests/functional_tests/wallet.py
|
|
+++ b/tests/functional_tests/wallet.py
|
|
@@ -34,8 +34,7 @@
|
|
|
|
from __future__ import print_function
|
|
import sys
|
|
-import os
|
|
-import errno
|
|
+import util_resources
|
|
|
|
from framework.wallet import Wallet
|
|
from framework.daemon import Daemon
|
|
@@ -54,24 +53,6 @@ class WalletTest():
|
|
self.change_password()
|
|
self.store()
|
|
|
|
- def remove_file(self, name):
|
|
- WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY']
|
|
- assert WALLET_DIRECTORY != ''
|
|
- try:
|
|
- os.unlink(WALLET_DIRECTORY + '/' + name)
|
|
- except OSError as e:
|
|
- if e.errno != errno.ENOENT:
|
|
- raise
|
|
-
|
|
- def remove_wallet_files(self, name):
|
|
- for suffix in ['', '.keys']:
|
|
- self.remove_file(name + suffix)
|
|
-
|
|
- def file_exists(self, name):
|
|
- WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY']
|
|
- assert WALLET_DIRECTORY != ''
|
|
- return os.path.isfile(WALLET_DIRECTORY + '/' + name)
|
|
-
|
|
def reset(self):
|
|
print('Resetting blockchain')
|
|
daemon = Daemon()
|
|
@@ -333,7 +314,7 @@ class WalletTest():
|
|
try: wallet.close_wallet()
|
|
except: pass
|
|
|
|
- self.remove_wallet_files('test1')
|
|
+ util_resources.remove_wallet_files('test1')
|
|
|
|
seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted'
|
|
res = wallet.restore_deterministic_wallet(seed = seed, filename = 'test1')
|
|
@@ -359,7 +340,7 @@ class WalletTest():
|
|
|
|
wallet.close_wallet()
|
|
|
|
- self.remove_wallet_files('test1')
|
|
+ util_resources.remove_wallet_files('test1')
|
|
|
|
def store(self):
|
|
print('Testing store')
|
|
@@ -369,22 +350,26 @@ class WalletTest():
|
|
try: wallet.close_wallet()
|
|
except: pass
|
|
|
|
- self.remove_wallet_files('test1')
|
|
+ util_resources.remove_wallet_files('test1')
|
|
|
|
seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted'
|
|
res = wallet.restore_deterministic_wallet(seed = seed, filename = 'test1')
|
|
assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
|
|
assert res.seed == seed
|
|
|
|
- self.remove_file('test1')
|
|
- assert self.file_exists('test1.keys')
|
|
- assert not self.file_exists('test1')
|
|
+ util_resources.remove_file('test1')
|
|
+ assert util_resources.file_exists('test1.keys')
|
|
+ assert not util_resources.file_exists('test1')
|
|
wallet.store()
|
|
- assert self.file_exists('test1.keys')
|
|
- assert self.file_exists('test1')
|
|
+ assert util_resources.file_exists('test1.keys')
|
|
+ assert util_resources.file_exists('test1')
|
|
|
|
wallet.close_wallet()
|
|
- self.remove_wallet_files('test1')
|
|
+
|
|
+ wallet.open_wallet(filename = 'test1', password = '')
|
|
+ wallet.close_wallet()
|
|
+
|
|
+ util_resources.remove_wallet_files('test1')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
diff --git a/tests/unit_tests/wipeable_string.cpp b/tests/unit_tests/wipeable_string.cpp
|
|
index ef6964f9e..25121a02e 100644
|
|
--- a/tests/unit_tests/wipeable_string.cpp
|
|
+++ b/tests/unit_tests/wipeable_string.cpp
|
|
@@ -211,3 +211,15 @@ TEST(wipeable_string, to_hex)
|
|
ASSERT_TRUE(epee::to_hex::wipeable_string(epee::span<const uint8_t>((const uint8_t*)"", 0)) == epee::wipeable_string(""));
|
|
ASSERT_TRUE(epee::to_hex::wipeable_string(epee::span<const uint8_t>((const uint8_t*)"abc", 3)) == epee::wipeable_string("616263"));
|
|
}
|
|
+
|
|
+TEST(wipeable_string, to_string)
|
|
+{
|
|
+ // Converting a wipeable_string to a string defeats the purpose of wipeable_string,
|
|
+ // but nice to know this works
|
|
+ std::string str;
|
|
+ {
|
|
+ epee::wipeable_string wipeable_str("foo");
|
|
+ str = std::string(wipeable_str.data(), wipeable_str.size());
|
|
+ }
|
|
+ ASSERT_TRUE(str == std::string("foo"));
|
|
+}
|
|
diff --git a/utils/python-rpc/framework/wallet.py b/utils/python-rpc/framework/wallet.py
|
|
index 1e10e1f86..ccf6ca93e 100644
|
|
--- a/utils/python-rpc/framework/wallet.py
|
|
+++ b/utils/python-rpc/framework/wallet.py
|
|
@@ -1138,3 +1138,38 @@ class Wallet(object):
|
|
'id': '0'
|
|
}
|
|
return self.rpc.send_json_rpc_request(frozen)
|
|
+
|
|
+ def setup_background_sync(self, background_sync_type = 'off', wallet_password = '', background_cache_password = ''):
|
|
+ setup_background_sync = {
|
|
+ 'method': 'setup_background_sync',
|
|
+ 'jsonrpc': '2.0',
|
|
+ 'params' : {
|
|
+ 'background_sync_type': background_sync_type,
|
|
+ 'wallet_password': wallet_password,
|
|
+ 'background_cache_password': background_cache_password,
|
|
+ },
|
|
+ 'id': '0'
|
|
+ }
|
|
+ return self.rpc.send_json_rpc_request(setup_background_sync)
|
|
+
|
|
+ def start_background_sync(self):
|
|
+ start_background_sync = {
|
|
+ 'method': 'start_background_sync',
|
|
+ 'jsonrpc': '2.0',
|
|
+ 'params' : {},
|
|
+ 'id': '0'
|
|
+ }
|
|
+ return self.rpc.send_json_rpc_request(start_background_sync)
|
|
+
|
|
+ def stop_background_sync(self, wallet_password = '', seed = '', seed_offset = ''):
|
|
+ stop_background_sync = {
|
|
+ 'method': 'stop_background_sync',
|
|
+ 'jsonrpc': '2.0',
|
|
+ 'params' : {
|
|
+ 'wallet_password': wallet_password,
|
|
+ 'seed': seed,
|
|
+ 'seed_offset': seed_offset,
|
|
+ },
|
|
+ 'id': '0'
|
|
+ }
|
|
+ return self.rpc.send_json_rpc_request(stop_background_sync)
|