summaryrefslogtreecommitdiff
path: root/patches/wownero
diff options
context:
space:
mode:
authorcyan <cyjan@mrcyjanek.net>2026-03-09 18:05:16 +0100
committerGitHub <noreply@github.com>2026-03-09 18:05:16 +0100
commit2c11591e02b907e63d8fd4fcb0a6559625934a95 (patch)
treedab95d36703f314a8ee9d6277a160df16833c4e5 /patches/wownero
parent411e8a1cdb3f4c2812d83f28c335d2a4eb18bd29 (diff)
reproducibility (#177)
* reproducibility * wip: ci fixes, drop generate_translations_header.c * minor fixes * fix patch * fix: toolchain * bump hash * fix: minor build issue fixes * fix: x86_64-w64-mingw32 * wip * wip * all updated :o * fix: reduce git size * update checksum remove CI * chore, more optimal dockerfile * update monero to v0.18.4.6 * update checksum * update * minor patch update * fix: no command specified * fix: correct path * alpine * stupid. * AAWASTREYDRFUGTIHYJHGUTYFRDTFYVGUBHINJHGTYFRDSRTXDTCFHBJ
Diffstat (limited to 'patches/wownero')
-rw-r--r--patches/wownero/0001-fix-missing-___clear_cache-when-targetting-iOS.patch (renamed from patches/wownero/0002-fix-missing-___clear_cache-when-targetting-iOS.patch)6
-rw-r--r--patches/wownero/0001-wallet-background-sync-with-just-the-view-key.patch4309
-rw-r--r--patches/wownero/0002-fix-is_trivially_copyable.patch (renamed from patches/wownero/0003-fix-is_trivially_copyable.patch)6
-rw-r--r--patches/wownero/0003-store-crash-fix.patch (renamed from patches/wownero/0004-store-crash-fix.patch)42
-rw-r--r--patches/wownero/0004-uint64_t-missing-definition-fix.patch (renamed from patches/wownero/0005-uint64_t-missing-definition-fix.patch)6
-rw-r--r--patches/wownero/0005-use-proper-error-handling-in-get_seed.patch (renamed from patches/wownero/0006-use-proper-error-handling-in-get_seed.patch)14
-rw-r--r--patches/wownero/0006-UR-functions.patch (renamed from patches/wownero/0007-UR-functions.patch)62
-rw-r--r--patches/wownero/0007-add-dummy-device-for-ledger.patch (renamed from patches/wownero/0008-add-dummy-device-for-ledger.patch)30
-rw-r--r--patches/wownero/0008-polyseed.patch (renamed from patches/wownero/0009-polyseed.patch)99
-rw-r--r--patches/wownero/0009-coin-control.patch (renamed from patches/wownero/0010-coin-control.patch)56
-rw-r--r--patches/wownero/0010-Add-hex-encoding-and-tx-key-getter-for-PendingTransc.patch (renamed from patches/wownero/0011-Add-hex-encoding-and-tx-key-getter-for-PendingTransc.patch)6
-rw-r--r--patches/wownero/0011-Add-recoverDeterministicWalletFromSpendKey.patch (renamed from patches/wownero/0012-Add-recoverDeterministicWalletFromSpendKey.patch)8
-rw-r--r--patches/wownero/0012-add-monero-submodule-support.patch (renamed from patches/wownero/0013-add-monero-submodule-support.patch)34
-rw-r--r--patches/wownero/0013-fix-iOS-depends-build.patch (renamed from patches/wownero/0014-fix-iOS-depends-build.patch)16
-rw-r--r--patches/wownero/0014-include-locale-only-when-targeting-WIN32.patch (renamed from patches/wownero/0015-include-locale-only-when-targeting-WIN32.patch)12
-rw-r--r--patches/wownero/0015-change-earliest-fork-height-message.patch (renamed from patches/wownero/0016-change-earliest-fork-height-message.patch)10
-rw-r--r--patches/wownero/0016-drop-generate_translations_header.c-requirement.patch132
-rw-r--r--patches/wownero/0017-pr-9880.patch25
-rw-r--r--patches/wownero/0018-fix-unary_function-__unary_function.patch28
-rw-r--r--patches/wownero/0022-fix-remove-flaky-test.patch27
-rw-r--r--patches/wownero/0025-depends-remove-icu4c-monero-project-monero-8880.patch25
21 files changed, 394 insertions, 4559 deletions
diff --git a/patches/wownero/0002-fix-missing-___clear_cache-when-targetting-iOS.patch b/patches/wownero/0001-fix-missing-___clear_cache-when-targetting-iOS.patch
index ca9ccfd..6b5dbe0 100644
--- a/patches/wownero/0002-fix-missing-___clear_cache-when-targetting-iOS.patch
+++ b/patches/wownero/0001-fix-missing-___clear_cache-when-targetting-iOS.patch
@@ -1,7 +1,7 @@
-From 4828befb3843764eaaa5e5ea489cde6d101d71ce Mon Sep 17 00:00:00 2001
+From 8de3cd7566eb3a6f3dd88d1e4126d2b16cfdea14 Mon Sep 17 00:00:00 2001
From: Czarek Nakamoto <cyjan@mrcyjanek.net>
Date: Wed, 23 Oct 2024 15:18:21 +0200
-Subject: [PATCH 02/15] fix missing ___clear_cache when targetting iOS
+Subject: [PATCH 01/16] fix missing ___clear_cache when targetting iOS
---
external/randomwow | 2 +-
@@ -15,5 +15,5 @@ index 27b099b6d..6f30d4b92 160000
-Subproject commit 27b099b6dd6fef6e17f58c6dfe00009e9c5df587
+Subproject commit 6f30d4b924fecb231e5b683915cc75d18b3b5866
--
-2.48.0
+2.51.0
diff --git a/patches/wownero/0001-wallet-background-sync-with-just-the-view-key.patch b/patches/wownero/0001-wallet-background-sync-with-just-the-view-key.patch
deleted file mode 100644
index e45deb1..0000000
--- a/patches/wownero/0001-wallet-background-sync-with-just-the-view-key.patch
+++ /dev/null
@@ -1,4309 +0,0 @@
-From 6ebd4546355d3d6ed82e3d30a45ecb06310b958e Mon Sep 17 00:00:00 2001
-From: j-berman <justinberman@protonmail.com>
-Date: Thu, 13 Oct 2022 18:33:33 -0700
-Subject: [PATCH 01/15] wallet: background sync with just the view key
-
-- When background syncing, the wallet wipes the spend key
-from memory and processes all new transactions. The wallet saves
-all receives, spends, and "plausible" spends of receives the
-wallet does not know key images for.
-- When background sync disabled, the wallet processes all
-background synced txs and then clears the background sync cache.
-- Adding "plausible" spends to the background sync cache ensures
-that the wallet does not need to query the daemon to see if any
-received outputs were spent while background sync was enabled.
-This would harm privacy especially for users of 3rd party daemons.
-- To enable the feature in the CLI wallet, the user can set
-background-sync to reuse-wallet-password or
-custom-background-password and the wallet automatically syncs in
-the background when the wallet locks, then processes all
-background synced txs when the wallet is unlocked.
-- The custom-background-password option enables the user to
-open a distinct background wallet that only has a view key saved
-and can be opened/closed/synced separately from the main wallet.
-When the main wallet opens, it processes the background wallet's
-cache.
-- To enable the feature in the RPC wallet, there is a new
-`/setup_background_sync` endpoint.
-- HW, multsig and view-only wallets cannot background sync.
----
- src/cryptonote_basic/account.cpp | 11 +
- src/cryptonote_basic/account.h | 1 +
- src/cryptonote_config.h | 2 +
- src/simplewallet/simplewallet.cpp | 204 +++-
- 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 | 1028 ++++++++++++++++--
- src/wallet/wallet2.h | 156 ++-
- 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 | 400 ++++++-
- 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 | 42 +
- 20 files changed, 2336 insertions(+), 129 deletions(-)
-
-diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp
-index 2ac455fda..4e87d4477 100644
---- a/src/cryptonote_basic/account.cpp
-+++ b/src/cryptonote_basic/account.cpp
-@@ -152,6 +152,17 @@ DISABLE_VS_WARNINGS(4244 4345)
- m_keys.m_multisig_keys.clear();
- }
- //-----------------------------------------------------------------
-+ 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 2ee9545d4..93d1d28f0 100644
---- a/src/cryptonote_basic/account.h
-+++ b/src/cryptonote_basic/account.h
-@@ -95,6 +95,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 8c0d3ce20..8b5091a46 100644
---- a/src/cryptonote_config.h
-+++ b/src/cryptonote_config.h
-@@ -253,6 +253,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 e0a08eec0..83b56c3f4 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)
-+
- static std::string get_human_readable_timespan(std::chrono::seconds seconds);
- static std::string get_human_readable_timespan(uint64_t seconds);
-
-@@ -325,7 +336,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;
- }
-@@ -335,6 +346,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;
-@@ -452,6 +468,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);
-@@ -805,6 +856,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()) {
-@@ -836,6 +888,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)
-@@ -913,6 +966,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;
- {
-@@ -1059,6 +1113,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())
- {
-@@ -2195,6 +2250,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");
-@@ -2234,6 +2290,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();
-@@ -3005,6 +3062,57 @@ 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;
-+ }
-+
-+ tools::wallet2::BackgroundSyncType background_sync_type;
-+ if (!parse_background_sync_type(args[1], background_sync_type))
-+ {
-+ fail_msg_writer() << tr("invalid option");
-+ return true;
-+ }
-+
-+ const auto pwd_container = get_and_verify_password();
-+ if (!pwd_container)
-+ 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();
-@@ -3237,6 +3345,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);
-@@ -3459,6 +3568,8 @@ simple_wallet::simple_wallet()
- " Ignore outputs of amount above this threshold when spending. Value 0 is translated to the maximum value (18 million) which disables this filter.\n "
- "ignore-outputs-below <amount>\n "
- " Ignore outputs of amount below this threshold when spending.\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 "
- "track-uses <1|0>\n "
- " Whether to keep track of owned outputs uses.\n "
- "setup-background-mining <1|0>\n "
-@@ -3879,6 +3990,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");
-@@ -3897,6 +4009,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) { \
-@@ -3950,6 +4063,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"));
-@@ -4904,7 +5018,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;
-
-@@ -5208,6 +5325,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) <<
-@@ -5415,6 +5534,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)
- {
-@@ -6287,6 +6410,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");
-@@ -6584,11 +6708,27 @@ void simple_wallet::check_for_inactivity_lock(bool user)
- " B B " << 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)
- {
-@@ -6600,8 +6740,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 */ }
- }
-@@ -6628,6 +6776,7 @@ bool simple_wallet::on_command(bool (simple_wallet::*cmd)(const std::vector<std:
- bool simple_wallet::transfer_main(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;
-
-@@ -7056,6 +7205,7 @@ bool simple_wallet::transfer_main(const std::vector<std::string> &args_, bool ca
- //----------------------------------------------------------------------------------------------------
- bool simple_wallet::transfer(const std::vector<std::string> &args_)
- {
-+ CHECK_IF_BACKGROUND_SYNCING("cannot transfer");
- if (args_.size() < 1)
- {
- PRINT_USAGE(USAGE_TRANSFER);
-@@ -7068,6 +7218,7 @@ bool simple_wallet::transfer(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;
-
-@@ -7175,6 +7326,7 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_)
- //----------------------------------------------------------------------------------------------------
- bool simple_wallet::sweep_main(uint32_t account, uint64_t below, const std::vector<std::string> &args_)
- {
-+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep");
- auto print_usage = [this, account, below]()
- {
- if (below)
-@@ -7456,6 +7608,7 @@ bool simple_wallet::sweep_main(uint32_t account, uint64_t below, const std::vect
- //----------------------------------------------------------------------------------------------------
- bool simple_wallet::sweep_single(const std::vector<std::string> &args_)
- {
-+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep");
- if (!try_connect_to_daemon())
- return true;
-
-@@ -7694,12 +7847,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, 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())
- {
-@@ -7720,6 +7875,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)
- {
-@@ -7738,6 +7894,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)
- {
-@@ -7799,6 +7956,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;
-@@ -7980,6 +8138,8 @@ bool simple_wallet::sign_transfer(const std::vector<std::string> &args_)
- return true;
- }
-
-+ CHECK_IF_BACKGROUND_SYNCING("cannot sign transfer");
-+
- bool export_raw = false;
- std::string unsigned_filename = "unsigned_wownero_tx";
- if (args_.size() > 2 || (args_.size() == 2 && args_[0] != "export_raw"))
-@@ -8086,6 +8246,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)
-@@ -8126,6 +8288,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) {
-@@ -8202,6 +8366,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);
-@@ -8408,6 +8574,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");
-@@ -8492,6 +8659,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");
-@@ -9192,6 +9360,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;
-
-@@ -9489,6 +9659,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)");
-@@ -9519,6 +9690,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]))
- {
-@@ -9540,6 +9712,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)
-@@ -9564,6 +9737,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)
- {
-@@ -9587,6 +9761,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)
-@@ -9704,6 +9879,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)
-@@ -9716,6 +9892,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)
- {
-@@ -9741,6 +9918,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)
-@@ -9759,6 +9937,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];
-@@ -9905,6 +10084,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)
- {
- }
-@@ -9965,6 +10146,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);
-@@ -9993,6 +10176,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);
-@@ -10018,6 +10203,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 = "";
-@@ -10034,6 +10221,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);
-@@ -10092,6 +10281,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;
-@@ -10103,6 +10294,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");
-@@ -10210,6 +10402,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())
-@@ -10263,6 +10456,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");
-@@ -10371,6 +10565,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;
-@@ -10420,6 +10615,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 d641dca1b..b98a40713 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 e81265ad3..e868fa039 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 {
-@@ -792,6 +826,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);
-@@ -805,6 +841,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);
- }
-
-@@ -828,6 +866,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);
-@@ -988,6 +1028,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);
- }
-
-@@ -1105,6 +1147,8 @@ void WalletImpl::refreshAsync()
-
- bool WalletImpl::rescanBlockchain()
- {
-+ if (checkBackgroundSync("cannot rescan blockchain"))
-+ return false;
- clearStatus();
- m_refreshShouldRescan = true;
- doRefresh();
-@@ -1113,6 +1157,8 @@ bool WalletImpl::rescanBlockchain()
-
- void WalletImpl::rescanBlockchainAsync()
- {
-+ if (checkBackgroundSync("cannot rescan blockchain"))
-+ return;
- m_refreshShouldRescan = true;
- refreshAsync();
- }
-@@ -1136,7 +1182,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();
-@@ -1156,6 +1202,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);
-@@ -1179,6 +1227,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
- {
-@@ -1199,6 +1249,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;
-@@ -1222,6 +1274,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);
-@@ -1252,6 +1306,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);
-@@ -1284,6 +1340,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.")));
-@@ -1322,8 +1380,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
-@@ -1336,10 +1472,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});
-@@ -1353,6 +1493,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);
-@@ -1366,12 +1508,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();
-@@ -1384,6 +1530,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();
-
-@@ -1524,6 +1672,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;
-@@ -1690,6 +1841,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);
-@@ -1823,11 +1977,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;
- }
-@@ -1841,6 +1999,8 @@ std::string WalletImpl::getCacheAttribute(const std::string &key) const
-
- bool WalletImpl::setUserNote(const std::string &txid, const std::string &note)
- {
-+ 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;
-@@ -1852,6 +2012,8 @@ bool WalletImpl::setUserNote(const std::string &txid, const std::string &note)
-
- 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 "";
-@@ -1862,6 +2024,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))
- {
-@@ -1946,6 +2111,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))
- {
-@@ -2002,6 +2170,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))
- {
-@@ -2044,6 +2215,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();
-@@ -2090,6 +2264,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);
- }
-@@ -2217,6 +2394,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);
-@@ -2285,9 +2472,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
-@@ -2391,6 +2576,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);
-@@ -2409,6 +2612,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 ec2d7e9b3..1f199a72c 100644
---- a/src/wallet/api/wallet.h
-+++ b/src/wallet/api/wallet.h
-@@ -171,6 +171,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;
-@@ -239,6 +246,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;
-@@ -253,6 +261,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 71991df0d..e349df176 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;
-@@ -936,6 +942,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 478101af2..535005ab1 100644
---- a/src/wallet/wallet2.cpp
-+++ b/src/wallet/wallet2.cpp
-@@ -157,6 +157,8 @@ static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1";
-
- static const std::string ASCII_OUTPUT_MAGIC = "WowneroAsciiDataV1";
-
-+static const std::string BACKGROUND_WALLET_SUFFIX = ".background";
-+
- boost::mutex tools::wallet2::default_daemon_address_lock;
- std::string tools::wallet2::default_daemon_address = "";
-
-@@ -1009,14 +1011,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;
-@@ -1104,7 +1106,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;
-@@ -1221,6 +1223,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(BackgroundMiningNo),
-@@ -1869,6 +1876,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())
-@@ -2178,11 +2188,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 wownero"));
-@@ -2194,7 +2204,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;
-@@ -2451,6 +2461,22 @@ 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();
-+ background_synced_tx_t bgs_tx = {
-+ .index_in_background_sync_data = bgs_idx,
-+ .tx = tx,
-+ .output_indices = o_indices,
-+ .height = height,
-+ .block_timestamp = ts,
-+ .double_spend_seen = double_spend_seen
-+ };
-+ LOG_PRINT_L2("Adding received tx " << txid << " to background sync data (idx=" << bgs_idx << ")");
-+ m_background_sync_data.txs.insert({txid, std::move(bgs_tx)});
-+ }
- }
-
- for(size_t o: outs)
-@@ -2476,7 +2502,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
-@@ -2666,10 +2692,25 @@ 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();
-+ background_synced_tx_t bgs_tx = {
-+ .index_in_background_sync_data = bgs_idx,
-+ .tx = tx,
-+ .output_indices = o_indices,
-+ .height = height,
-+ .block_timestamp = ts,
-+ .double_spend_seen = double_spend_seen
-+ };
-+ LOG_PRINT_L2("Adding spent tx " << txid << " to background sync data (idx=" << bgs_idx << ")");
-+ m_background_sync_data.txs.insert({txid, std::move(bgs_tx)});
-+ }
- }
- }
-
-- 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;
-@@ -2683,7 +2724,27 @@ 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();
-+ background_synced_tx_t bgs_tx = {
-+ .index_in_background_sync_data = bgs_idx,
-+ .tx = tx,
-+ .output_indices = o_indices,
-+ .height = height,
-+ .block_timestamp = ts,
-+ .double_spend_seen = double_spend_seen
-+ };
-+ LOG_PRINT_L2("Adding plausible spent tx " << txid << " to background sync data (idx=" << bgs_idx << ")");
-+ m_background_sync_data.txs.insert({txid, std::move(bgs_tx)});
-+ }
- }
- }
- }
-@@ -2693,7 +2754,24 @@ 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();
-+ background_synced_tx_t bgs_tx = {
-+ .index_in_background_sync_data = bgs_idx,
-+ .tx = tx,
-+ .output_indices = o_indices,
-+ .height = height,
-+ .block_timestamp = ts,
-+ .double_spend_seen = double_spend_seen
-+ };
-+ LOG_PRINT_L2("Adding plausible spent tx " << txid << " to background sync data (idx=" << bgs_idx << ")");
-+ m_background_sync_data.txs.insert({txid, std::move(bgs_tx)});
-+ }
-+ }
- }
- }
- }
-@@ -3066,8 +3144,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;
-
- {
-@@ -3094,7 +3172,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)
- {
-@@ -3606,6 +3684,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)
- {
-@@ -4177,6 +4258,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)));
- }
-@@ -4262,6 +4345,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();
-
-@@ -4336,8 +4427,12 @@ void wallet2::handle_reorg(uint64_t height, std::map<std::pair<uint64_t, uint64_
- // C
- THROW_WALLET_EXCEPTION_IF(height < m_blockchain.offset() && m_blockchain.size() > m_blockchain.offset(),
- 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());
- }
-@@ -4347,6 +4442,7 @@ bool wallet2::deinit()
- if(m_is_initialized) {
- m_is_initialized = false;
- unlock_keys_file();
-+ unlock_background_keys_file();
- m_account.deinit();
- }
- return true;
-@@ -4373,6 +4469,7 @@ 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 = background_sync_data_t{};
- return true;
- }
- //----------------------------------------------------------------------------------------------------
-@@ -4391,13 +4488,30 @@ 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 = background_sync_data_t{};
-
- 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
-@@ -4409,16 +4523,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);
-@@ -4430,26 +4563,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);
-@@ -4584,6 +4718,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());
-
-@@ -4641,6 +4778,12 @@ 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());
-+ }
-+
- // Serialize the JSON object
- rapidjson::StringBuffer buffer;
- rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
-@@ -4667,13 +4810,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);
-@@ -4732,8 +4943,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())
- {
-@@ -4771,6 +4998,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 = BackgroundMiningNo;
-@@ -4788,6 +5016,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 = true;
-+ m_custom_background_key = boost::none;
- }
- else if(json.IsObject())
- {
-@@ -5024,6 +5253,39 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
- m_credits_target = field_credits_target;
- 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, 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");
-+ }
- }
- else
- {
-@@ -5087,12 +5349,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;
- }
-@@ -5107,11 +5374,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;
- }
-@@ -5129,7 +5397,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;
-@@ -5146,9 +5414,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())
- {
-@@ -5173,6 +5454,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;
- }
-
-@@ -5184,9 +5466,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);
-@@ -5862,11 +6142,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)
-@@ -5900,6 +6199,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 + 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;
-@@ -6135,10 +6444,81 @@ 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()));
-+
-+ // Wallets used to wipe, but not erase, old unused multisig key info, which lead to huge memory leaks.
-+ // Here we erase these multisig keys if they're zero'd out to free up space.
-+ for (auto &td : m_transfers)
-+ {
-+ auto mk_it = td.m_multisig_k.begin();
-+ while (mk_it != td.m_multisig_k.end())
-+ {
-+ if (*mk_it == rct::zero())
-+ mk_it = td.m_multisig_k.erase(mk_it);
-+ else
-+ ++mk_it;
-+ }
-+ }
-+
-+ 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::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)
- {
-@@ -6165,7 +6545,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;
-@@ -6255,60 +6635,76 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass
- m_account_public_address.m_view_public_key != m_account.get_keys().m_account_address.m_view_public_key,
- error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file);
- }
-+}
-+//----------------------------------------------------------------------------------------------------
-+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_persistent_rpc_client_id)
-- set_rpc_client_secret_key(rct::rct2sk(rct::skGen()));
-+ 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);
-
-- // Wallets used to wipe, but not erase, old unused multisig key info, which lead to huge memory leaks.
-- // Here we erase these multisig keys if they're zero'd out to free up space.
-- for (auto &td : m_transfers)
-+ // Reset the background cache after processing
-+ reset_background_sync_data(m_background_sync_data);
-+ }
-+ else if (m_background_sync_type == BackgroundSyncCustomPassword)
- {
-- auto mk_it = td.m_multisig_k.begin();
-- while (mk_it != td.m_multisig_k.end())
-+ // 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)
- {
-- if (*mk_it == rct::zero())
-- mk_it = td.m_multisig_k.erase(mk_it);
-- else
-- ++mk_it;
-+ MDEBUG("Background keys file not found, restoring");
-+ store_background_keys(m_custom_background_key.get());
- }
-- }
-
-- cryptonote::block genesis;
-- generate_genesis(genesis);
-- crypto::hash genesis_hash = get_block_hash(genesis);
-+ 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;
-+ }
-
-- 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);
-- }
-+ MDEBUG("Loading background cache");
-
-- trim_hashchain();
-+ // 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;
-
-- if (get_num_subaddress_accounts() == 0)
-- add_subaddress_account(tr("Primary account"));
-+ cryptonote::account_base account = m_account;
-+ account.forget_spend_key();
-+ background_w2->m_account = 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);
-+ // 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*/);
- }
-- catch (const std::exception &e)
-+ else
- {
-- MERROR("Failed to initialize MMS, it will be unusable");
-+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, "unknown background sync type");
- }
- }
- //----------------------------------------------------------------------------------------------------
-@@ -6390,6 +6786,8 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
- same_file = canonical_old_path == canonical_new_path;
- }
-
-+ THROW_WALLET_EXCEPTION_IF(m_is_background_wallet && !same_file, error::wallet_internal_error,
-+ "Cannot save background wallet files to a different location");
-
- if (!same_file)
- {
-@@ -6406,6 +6804,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();
-@@ -6499,6 +6912,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()
-@@ -6516,7 +6945,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;
- }
-@@ -8586,6 +9015,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
-@@ -13909,6 +14366,413 @@ 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_t>> 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_t>& l, const std::pair<crypto::hash, background_synced_tx_t>& 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");
-+ THROW_WALLET_EXCEPTION_IF(m_wallet_file.empty(), error::wallet_internal_error,
-+ "No wallet file known, can't store background cache");
-+
-+ 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");
-+
-+ THROW_WALLET_EXCEPTION_IF(m_wallet_file.empty(), error::wallet_internal_error,
-+ "No wallet file known, can't store 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 b620ea265..632298726 100644
---- a/src/wallet/wallet2.h
-+++ b/src/wallet/wallet2.h
-@@ -256,6 +256,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,
-@@ -282,7 +296,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()));
-@@ -792,6 +811,54 @@ private:
- END_SERIALIZE()
- };
-
-+ struct background_synced_tx_t
-+ {
-+ 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 = false;
-+ uint64_t start_height = 0;
-+ serializable_unordered_map<crypto::hash, background_synced_tx_t> 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
-@@ -980,7 +1047,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;}
-
-@@ -1076,6 +1144,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;
-@@ -1283,11 +1352,17 @@ private:
- return;
- }
- a & m_has_ever_refreshed_from_node;
-+ if(ver < 31)
-+ {
-+ m_background_sync_data = background_sync_data_t{};
-+ return;
-+ }
-+ a & m_background_sync_data;
- }
-
- BEGIN_SERIALIZE_OBJECT()
- MAGIC_FIELD("wownero wallet cache")
-- VERSION_FIELD(1)
-+ VERSION_FIELD(2)
- FIELD(m_blockchain)
- FIELD(m_transfers)
- FIELD(m_account_public_address)
-@@ -1319,6 +1394,12 @@ private:
- return true;
- }
- FIELD(m_has_ever_refreshed_from_node)
-+ if (version < 2)
-+ {
-+ m_background_sync_data = background_sync_data_t{};
-+ return true;
-+ }
-+ FIELD(m_background_sync_data)
- END_SERIALIZE()
-
- /*!
-@@ -1334,6 +1415,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);
-@@ -1382,6 +1465,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; }
-@@ -1696,6 +1782,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; };
-@@ -1731,6 +1820,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
-@@ -1744,6 +1836,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);
-@@ -1752,6 +1845,15 @@ 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);
-+ /*
-+ * clear_user_data clears data created by the user, which is mostly data
-+ * that a view key cannot identify on chain. This function was initially
-+ * added to ensure that a "background" wallet (a wallet that syncs with just
-+ * a view key hot in memory) does not have any sensitive data loaded that it
-+ * does not need in order to sync. Future devs should take care to ensure
-+ * that this function deletes data that is not useful for background syncing
-+ */
-+ 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 &current_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);
-@@ -1803,10 +1905,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);
-@@ -1922,6 +2037,8 @@ 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_show_wallet_name_when_locked;
- uint32_t m_inactivity_lock_timeout;
- BackgroundMiningSetupType m_setup_background_mining;
-@@ -1967,6 +2084,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;
-@@ -1974,6 +2092,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;
-@@ -1989,9 +2108,13 @@ private:
-
- static boost::mutex default_daemon_address_lock;
- static std::string default_daemon_address;
-+
-+ bool m_background_syncing;
-+ bool m_processing_background_cache;
-+ background_sync_data_t m_background_sync_data;
- };
- }
--BOOST_CLASS_VERSION(tools::wallet2, 30)
-+BOOST_CLASS_VERSION(tools::wallet2, 31)
- BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 12)
- BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1)
- BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0)
-@@ -2007,6 +2130,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_t, 0)
-+BOOST_CLASS_VERSION(tools::wallet2::background_sync_data_t, 0)
-
- namespace boost
- {
-@@ -2505,6 +2630,29 @@ namespace boost
- return;
- a & x.multisig_sigs;
- }
-+
-+ template <class Archive>
-+ inline void serialize(Archive& a, tools::wallet2::background_synced_tx_t &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 c077313d4..c54cd3499 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
-@@ -97,6 +98,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
-
-@@ -304,6 +308,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
- {
-@@ -947,6 +961,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 1c7c45034..21e69ea0e 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)
-@@ -581,6 +632,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) {
-@@ -618,6 +670,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);
-@@ -680,6 +733,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);
-@@ -697,6 +751,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);
-@@ -712,6 +767,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)
- {
-@@ -731,6 +787,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);
-@@ -746,6 +803,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, "");
-@@ -761,6 +819,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);
-@@ -791,6 +850,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())
-@@ -819,6 +879,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())
-@@ -847,6 +908,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())
-@@ -874,6 +936,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++)
-@@ -1203,6 +1267,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))
-@@ -1284,6 +1349,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;
-@@ -1553,6 +1619,7 @@ namespace tools
- }
-
- CHECK_MULTISIG_ENABLED();
-+ CHECK_IF_BACKGROUND_SYNCING();
-
- try
- {
-@@ -2114,6 +2181,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;
-@@ -2142,6 +2210,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());
- }
-@@ -2163,6 +2232,7 @@ namespace tools
- er.message = "Command unavailable in restricted mode.";
- return false;
- }
-+ CHECK_IF_BACKGROUND_SYNCING();
-
- try
- {
-@@ -2176,6 +2246,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);
-@@ -2185,6 +2328,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 == "")
-@@ -2277,6 +2421,7 @@ namespace tools
- er.message = "Command unavailable in restricted mode.";
- return false;
- }
-+ CHECK_IF_BACKGROUND_SYNCING();
-
- if (req.txids.size() != req.notes.size())
- {
-@@ -2349,6 +2494,7 @@ namespace tools
- er.message = "Command unavailable in restricted mode.";
- return false;
- }
-+ CHECK_IF_BACKGROUND_SYNCING();
-
- m_wallet->set_attribute(req.key, req.value);
-
-@@ -2376,6 +2522,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))
-@@ -2467,6 +2614,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))
-@@ -2583,6 +2731,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)
-@@ -2825,6 +2974,7 @@ namespace tools
- er.message = "command not supported by HW wallet";
- return false;
- }
-+ CHECK_IF_BACKGROUND_SYNCING();
-
- try
- {
-@@ -2854,6 +3004,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))
-@@ -2879,6 +3030,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);
-@@ -2915,6 +3067,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;
-@@ -2983,6 +3136,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())
- {
-@@ -3028,6 +3182,7 @@ namespace tools
- er.message = "Command unavailable in restricted mode.";
- return false;
- }
-+ CHECK_IF_BACKGROUND_SYNCING();
-
- cryptonote::address_parse_info info;
- er.message = "";
-@@ -3070,6 +3225,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())
-@@ -3132,6 +3288,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())
-@@ -3202,6 +3359,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();
-@@ -3241,6 +3399,7 @@ namespace tools
- er.message = "Command unavailable in restricted mode.";
- return false;
- }
-+ CHECK_IF_BACKGROUND_SYNCING();
- try
- {
- m_wallet->rescan_spent();
-@@ -3505,6 +3664,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
-@@ -4032,6 +4192,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;
-@@ -4059,6 +4220,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 2ce39f667..72a35eb73 100644
---- a/src/wallet/wallet_rpc_server_commands_defs.h
-+++ b/src/wallet/wallet_rpc_server_commands_defs.h
-@@ -2698,5 +2698,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 541d29f86..4756c191c 100644
---- a/src/wallet/wallet_rpc_server_error_codes.h
-+++ b/src/wallet/wallet_rpc_server_error_codes.h
-@@ -81,3 +81,5 @@
- #define WALLET_RPC_ERROR_CODE_DISABLED -48
- #define WALLET_RPC_ERROR_CODE_PROXY_ALREADY_DEFINED -49
- #define WALLET_RPC_ERROR_CODE_NONZERO_UNLOCK_TIME -50
-+#define WALLET_RPC_ERROR_CODE_IS_BACKGROUND_WALLET -51
-+#define WALLET_RPC_ERROR_CODE_IS_BACKGROUND_SYNCING -52
-diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py
-index 4063911f4..60eb09a10 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')
-@@ -840,12 +854,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()
-@@ -1127,5 +1135,385 @@ class TransferTest():
- except AssertionError:
- pass
-
-+ def check_background_sync(self):
-+ daemon = Daemon()
-+
-+ print('Testing background sync')
-+
-+ # 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
-+
-+ # Background sync type options
-+ reuse_password = sender_wallet.background_sync_options.reuse_password
-+ custom_password = sender_wallet.background_sync_options.custom_password
-+
-+ # 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 = sender_wallet.background_sync_options.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
-+ sender_wallet = self.wallet[0]
-+ reuse_password = sender_wallet.background_sync_options.reuse_password
-+ custom_password = sender_wallet.background_sync_options.custom_password
-+
-+ for background_sync_type in [reuse_password, custom_password]:
-+ # Set up wallet saved to disk
-+ 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])
-+
- if __name__ == '__main__':
- TransferTest().run_test()
-diff --git a/tests/functional_tests/util_resources.py b/tests/functional_tests/util_resources.py
-index e030312da..3ca6fdb86 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', '.address.txt']:
-+ 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..bff33a561 100644
---- a/utils/python-rpc/framework/wallet.py
-+++ b/utils/python-rpc/framework/wallet.py
-@@ -1138,3 +1138,45 @@ class Wallet(object):
- 'id': '0'
- }
- return self.rpc.send_json_rpc_request(frozen)
-+
-+ class BackgroundSyncOptions(object):
-+ def __init__(self):
-+ self.off = 'off'
-+ self.reuse_password = 'reuse-wallet-password'
-+ self.custom_password = 'custom-background-password'
-+ background_sync_options = BackgroundSyncOptions()
-+
-+ def setup_background_sync(self, background_sync_type = background_sync_options.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)
---
-2.48.0
-
diff --git a/patches/wownero/0003-fix-is_trivially_copyable.patch b/patches/wownero/0002-fix-is_trivially_copyable.patch
index 5b81f2d..e6da812 100644
--- a/patches/wownero/0003-fix-is_trivially_copyable.patch
+++ b/patches/wownero/0002-fix-is_trivially_copyable.patch
@@ -1,7 +1,7 @@
-From d4d406961073b5f9d3cc46a6cedaf73a378e1ea6 Mon Sep 17 00:00:00 2001
+From ab667a9b129984abbbc252963cf4f471c8fe29ba Mon Sep 17 00:00:00 2001
From: cyan <cyjan@mrcyjanek.net>
Date: Tue, 22 Oct 2024 10:23:18 +0000
-Subject: [PATCH 03/15] fix is_trivially_copyable
+Subject: [PATCH 02/16] fix is_trivially_copyable
---
contrib/epee/include/span.h | 2 --
@@ -28,5 +28,5 @@ index 01dc387d6..5e3af4d11 100644
return {reinterpret_cast<const std::uint8_t*>(std::addressof(src)), sizeof(T)};
}
--
-2.48.0
+2.51.0
diff --git a/patches/wownero/0004-store-crash-fix.patch b/patches/wownero/0003-store-crash-fix.patch
index 52104dc..1bc6a4a 100644
--- a/patches/wownero/0004-store-crash-fix.patch
+++ b/patches/wownero/0003-store-crash-fix.patch
@@ -1,7 +1,7 @@
-From 799d1825696483da743ff2f92a773fcfbef08557 Mon Sep 17 00:00:00 2001
+From 45a4148e08f61fe6d85b7245e7ee445b8db97273 Mon Sep 17 00:00:00 2001
From: Czarek Nakamoto <cyjan@mrcyjanek.net>
Date: Sat, 11 May 2024 16:25:10 +0200
-Subject: [PATCH 04/15] store crash fix
+Subject: [PATCH 03/16] store crash fix
Monero wallet crashes (sometimes) when it is syncing,
while the proper solution (that can be seen in feather)
@@ -36,14 +36,22 @@ would just wait for it to finish before actually storing).
Also imo store() functin should store the wallet, no matter
the current state.
---
- src/wallet/api/wallet.cpp | 25 ++++++++++++-------------
+ external/randomwow | 2 +-
+ src/wallet/api/wallet.cpp | 23 +++++++++++------------
src/wallet/api/wallet.h | 1 -
src/wallet/wallet2.cpp | 12 +++++++++++-
src/wallet/wallet2.h | 3 +++
- 4 files changed, 26 insertions(+), 15 deletions(-)
+ 5 files changed, 26 insertions(+), 15 deletions(-)
+diff --git a/external/randomwow b/external/randomwow
+index 6f30d4b92..27b099b6d 160000
+--- a/external/randomwow
++++ b/external/randomwow
+@@ -1 +1 @@
+-Subproject commit 6f30d4b924fecb231e5b683915cc75d18b3b5866
++Subproject commit cd137b1ea7bb9f0bcb5e77b39a5c1e08ca4b4fed
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
-index e868fa039..899ef044a 100644
+index af420569c..d53372fc9 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -55,8 +55,8 @@ using namespace cryptonote;
@@ -74,7 +82,7 @@ index e868fa039..899ef044a 100644
try {
if (path.empty()) {
m_wallet->store();
-@@ -2448,10 +2449,10 @@ void WalletImpl::refreshThreadFunc()
+@@ -2449,10 +2450,10 @@ void WalletImpl::refreshThreadFunc()
}
LOG_PRINT_L3(__FUNCTION__ << ": refresh lock acquired...");
@@ -87,13 +95,7 @@ index e868fa039..899ef044a 100644
LOG_PRINT_L3(__FUNCTION__ << ": refreshing...");
doRefresh();
}
-@@ -2481,12 +2482,12 @@ void WalletImpl::doRefresh()
- }
- m_wallet->find_and_save_rings(false);
- } else {
-- LOG_PRINT_L3(__FUNCTION__ << ": skipping refresh - daemon is not synced");
-+ LOG_PRINT_L3(__FUNCTION__ << ": skipping refresh - daemon is not synced");
- }
+@@ -2486,7 +2487,7 @@ void WalletImpl::doRefresh()
} catch (const std::exception &e) {
setStatusError(e.what());
break;
@@ -147,10 +149,10 @@ index 1f199a72c..ac7ce2f6a 100644
std::atomic<int> m_refreshIntervalMillis;
std::atomic<bool> m_refreshShouldRescan;
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
-index 535005ab1..4e66cdeae 100644
+index f111ad0d5..8d8ba33c5 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
-@@ -1192,6 +1192,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std
+@@ -1195,6 +1195,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std
m_upper_transaction_weight_limit(0),
m_run(true),
m_callback(0),
@@ -158,7 +160,7 @@ index 535005ab1..4e66cdeae 100644
m_trusted_daemon(false),
m_nettype(nettype),
m_multisig_rounds_passed(0),
-@@ -1412,6 +1413,14 @@ bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_u
+@@ -1415,6 +1416,14 @@ bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_u
return ret;
}
//----------------------------------------------------------------------------------------------------
@@ -173,7 +175,7 @@ index 535005ab1..4e66cdeae 100644
bool wallet2::set_proxy(const std::string &address)
{
return m_http_client->set_proxy(address);
-@@ -4107,8 +4116,9 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
+@@ -4151,8 +4160,9 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
// infer when we get an incoming output
bool first = true, last = false;
@@ -185,7 +187,7 @@ index 535005ab1..4e66cdeae 100644
std::vector<cryptonote::block_complete_entry> next_blocks;
std::vector<parsed_block> next_parsed_blocks;
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
-index 632298726..022d0696f 100644
+index 8ffad5675..a1d939a5a 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -1078,6 +1078,8 @@ private:
@@ -197,7 +199,7 @@ index 632298726..022d0696f 100644
void stop() { m_run.store(false, std::memory_order_relaxed); m_message_store.stop(); }
-@@ -1989,6 +1991,7 @@ private:
+@@ -1996,6 +1998,7 @@ private:
boost::recursive_mutex m_daemon_rpc_mutex;
@@ -206,5 +208,5 @@ index 632298726..022d0696f 100644
i_wallet2_callback* m_callback;
hw::device::device_type m_key_device_type;
--
-2.48.0
+2.51.0
diff --git a/patches/wownero/0005-uint64_t-missing-definition-fix.patch b/patches/wownero/0004-uint64_t-missing-definition-fix.patch
index 5035df2..8bfa59a 100644
--- a/patches/wownero/0005-uint64_t-missing-definition-fix.patch
+++ b/patches/wownero/0004-uint64_t-missing-definition-fix.patch
@@ -1,7 +1,7 @@
-From 5a9148c10ea29a42d8aa08d69e2f4559c8611655 Mon Sep 17 00:00:00 2001
+From faa6501f23d74a59f345a8542fdbb65f27540e74 Mon Sep 17 00:00:00 2001
From: Czarek Nakamoto <cyjan@mrcyjanek.net>
Date: Mon, 2 Sep 2024 16:40:31 +0200
-Subject: [PATCH 05/15] uint64_t missing definition fix
+Subject: [PATCH 04/16] uint64_t missing definition fix
---
contrib/epee/include/net/http_base.h | 2 +-
@@ -21,5 +21,5 @@ index 4af4da790..ae4c0d05e 100644
#include <string>
--
-2.48.0
+2.51.0
diff --git a/patches/wownero/0006-use-proper-error-handling-in-get_seed.patch b/patches/wownero/0005-use-proper-error-handling-in-get_seed.patch
index b9680fc..d581ebf 100644
--- a/patches/wownero/0006-use-proper-error-handling-in-get_seed.patch
+++ b/patches/wownero/0005-use-proper-error-handling-in-get_seed.patch
@@ -1,7 +1,7 @@
-From ee6aa49179833c930c1ec10ae2e877aeb87eb8c8 Mon Sep 17 00:00:00 2001
+From 04899e3877b7f801b21057f7e527b5fb846fb7bd Mon Sep 17 00:00:00 2001
From: Czarek Nakamoto <cyjan@mrcyjanek.net>
Date: Mon, 24 Jun 2024 10:49:12 +0200
-Subject: [PATCH 06/15] use proper error handling in get_seed
+Subject: [PATCH 05/16] use proper error handling in get_seed
---
src/wallet/api/wallet.cpp | 17 ++++++++++++-----
@@ -9,7 +9,7 @@ Subject: [PATCH 06/15] use proper error handling in get_seed
2 files changed, 16 insertions(+), 6 deletions(-)
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
-index 899ef044a..e16d8f83f 100644
+index d53372fc9..ec02a129d 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -826,12 +826,19 @@ bool WalletImpl::close(bool store)
@@ -38,10 +38,10 @@ index 899ef044a..e16d8f83f 100644
std::string WalletImpl::getSeedLanguage() const
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
-index 4e66cdeae..48267d647 100644
+index 8d8ba33c5..856645b6b 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
-@@ -1449,11 +1449,13 @@ bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeab
+@@ -1452,11 +1452,13 @@ bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeab
bool keys_deterministic = is_deterministic();
if (!keys_deterministic)
{
@@ -55,7 +55,7 @@ index 4e66cdeae..48267d647 100644
std::cout << "seed_language not set" << std::endl;
return false;
}
-@@ -1463,8 +1465,9 @@ bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeab
+@@ -1466,8 +1468,9 @@ bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeab
key = cryptonote::encrypt_key(key, passphrase);
if (!crypto::ElectrumWords::bytes_to_words(key, electrum_words, seed_language))
{
@@ -67,5 +67,5 @@ index 4e66cdeae..48267d647 100644
return true;
--
-2.48.0
+2.51.0
diff --git a/patches/wownero/0007-UR-functions.patch b/patches/wownero/0006-UR-functions.patch
index 0bdc695..b3cda8d 100644
--- a/patches/wownero/0007-UR-functions.patch
+++ b/patches/wownero/0006-UR-functions.patch
@@ -1,7 +1,7 @@
-From 031df7de0d75d93f78be732d5cac702b0ab193f0 Mon Sep 17 00:00:00 2001
+From 59084c084836343354f2e5b5dccafc26b5f7f02a Mon Sep 17 00:00:00 2001
From: tobtoht <tob@featherwallet.org>
Date: Tue, 12 Mar 2024 10:09:50 +0100
-Subject: [PATCH 07/15] UR functions
+Subject: [PATCH 06/16] UR functions
This commit adds UR functions for UR tasks,
I believe that the right place to get
@@ -24,7 +24,6 @@ Things broken in the commit
contrib/depends/hosts/darwin.mk | 2 +-
contrib/depends/toolchain.cmake.in | 2 +-
external/CMakeLists.txt | 1 +
- external/bc-ur | 1 +
src/device/device_ledger.cpp | 5 +-
src/wallet/CMakeLists.txt | 1 +
src/wallet/api/pending_transaction.cpp | 33 +++
@@ -36,14 +35,13 @@ Things broken in the commit
src/wallet/api/wallet2_api.h | 22 +-
src/wallet/wallet2.cpp | 141 +++++++----
src/wallet/wallet2.h | 3 +
- 17 files changed, 521 insertions(+), 60 deletions(-)
- create mode 160000 external/bc-ur
+ 16 files changed, 520 insertions(+), 60 deletions(-)
diff --git a/.gitmodules b/.gitmodules
-index 991071fbe..b24855d9b 100644
+index 98a0af1ab..c1c0d385d 100644
--- a/.gitmodules
+++ b/.gitmodules
-@@ -16,4 +16,7 @@
+@@ -15,4 +15,7 @@
path = external/randomwow
url = https://codeberg.org/wownero/RandomWOW
branch = 1.2.1-wow
@@ -53,7 +51,7 @@ index 991071fbe..b24855d9b 100644
+ url = https://github.com/MrCyjaneK/bc-ur
+ branch = misc
diff --git a/CMakeLists.txt b/CMakeLists.txt
-index b4b8c8089..88335ee9d 100644
+index c8edf7a45..c439e5300 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -96,7 +96,8 @@ enable_language(C ASM)
@@ -73,7 +71,7 @@ index b4b8c8089..88335ee9d 100644
+ #check_submodule(external/bc-ur)
check_submodule(external/miniupnp)
check_submodule(external/rapidjson)
- #check_submodule(external/trezor-common)
+ check_submodule(external/trezor-common)
diff --git a/contrib/depends/hosts/darwin.mk b/contrib/depends/hosts/darwin.mk
index 79d449054..83d83036b 100644
--- a/contrib/depends/hosts/darwin.mk
@@ -98,24 +96,17 @@ index f118c754e..f26655d68 100644
SET(LLVM_ENABLE_PIC OFF)
SET(LLVM_ENABLE_PIE OFF)
diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt
-index 29aed0cc6..dfdffe67c 100644
+index 649383aa2..d0b6d9b14 100644
--- a/external/CMakeLists.txt
+++ b/external/CMakeLists.txt
-@@ -69,4 +69,5 @@ endif()
+@@ -70,4 +70,5 @@ endif()
add_subdirectory(db_drivers)
add_subdirectory(easylogging++)
add_subdirectory(qrcodegen)
+add_subdirectory(bc-ur)
add_subdirectory(randomwow EXCLUDE_FROM_ALL)
-diff --git a/external/bc-ur b/external/bc-ur
-new file mode 160000
-index 000000000..d82e7c753
---- /dev/null
-+++ b/external/bc-ur
-@@ -0,0 +1 @@
-+Subproject commit d82e7c753e710b8000706dc3383b498438795208
diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp
-index 9961d13e7..8403d76e8 100644
+index 56a3a45c4..e1bfa7041 100644
--- a/src/device/device_ledger.cpp
+++ b/src/device/device_ledger.cpp
@@ -313,12 +313,13 @@ namespace hw {
@@ -282,7 +273,7 @@ index 30065a7fa..a94b23f75 100644
uint64_t minMixinCount() const override;
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
-index e16d8f83f..ee000e7ab 100644
+index ec02a129d..e04205e48 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -47,6 +47,7 @@
@@ -780,10 +771,10 @@ index e349df176..764adbfbf 100644
/*!
* \brief scanTransactions - scan a list of transaction ids, this operation may reveal the txids to the remote node and affect your privacy
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
-index 48267d647..e14d4d2fc 100644
+index 856645b6b..d89595cf8 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
-@@ -948,6 +948,16 @@ uint32_t get_subaddress_clamped_sum(uint32_t idx, uint32_t extra)
+@@ -953,6 +953,16 @@ uint32_t get_subaddress_clamped_sum(uint32_t idx, uint32_t extra)
return idx + extra;
}
@@ -800,7 +791,7 @@ index 48267d647..e14d4d2fc 100644
static void setup_shim(hw::wallet_shim * shim, tools::wallet2 * wallet)
{
shim->get_tx_pub_key_from_received_outs = std::bind(&tools::wallet2::get_tx_pub_key_from_received_outs, wallet, std::placeholders::_1);
-@@ -6998,6 +7008,25 @@ uint64_t wallet2::unlocked_balance(uint32_t index_major, bool strict, uint64_t *
+@@ -7039,6 +7049,25 @@ uint64_t wallet2::unlocked_balance(uint32_t index_major, bool strict, uint64_t *
return amount;
}
//----------------------------------------------------------------------------------------------------
@@ -826,7 +817,7 @@ index 48267d647..e14d4d2fc 100644
std::map<uint32_t, uint64_t> wallet2::balance_per_subaddress(uint32_t index_major, bool strict) const
{
std::map<uint32_t, uint64_t> amount_per_subaddr;
-@@ -7849,9 +7878,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin
+@@ -7890,9 +7919,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin
crypto::key_derivation derivation;
std::vector<crypto::key_derivation> additional_derivations;
@@ -837,7 +828,7 @@ index 48267d647..e14d4d2fc 100644
std::vector<crypto::public_key> additional_tx_pub_keys;
for (const crypto::secret_key &skey: txs[n].additional_tx_keys)
{
-@@ -11241,7 +11268,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
+@@ -11230,7 +11257,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
MDEBUG("Ignoring output " << i << " of amount " << print_money(td.amount()) << " which is below fractional threshold " << print_money(fractional_threshold));
continue;
}
@@ -846,7 +837,7 @@ index 48267d647..e14d4d2fc 100644
{
if (td.amount() > m_ignore_outputs_above || td.amount() < m_ignore_outputs_below)
{
-@@ -11291,9 +11318,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
+@@ -11280,9 +11307,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
LOG_PRINT_L2("Starting with " << num_nondust_outputs << " non-dust outputs and " << num_dust_outputs << " dust outputs");
@@ -864,7 +855,7 @@ index 48267d647..e14d4d2fc 100644
// if empty, put dummy entry so that the front can be referenced later in the loop
if (unused_dust_indices_per_subaddr.empty())
unused_dust_indices_per_subaddr.push_back({});
-@@ -13920,33 +13953,40 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle
+@@ -13911,33 +13944,40 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle
bool wallet2::export_key_images(const std::string &filename, bool all) const
{
@@ -928,7 +919,7 @@ index 48267d647..e14d4d2fc 100644
//----------------------------------------------------------------------------------------------------
std::pair<uint64_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> wallet2::export_key_images(bool all) const
{
-@@ -14001,53 +14041,60 @@ std::pair<uint64_t, std::vector<std::pair<crypto::key_image, crypto::signature>>
+@@ -13992,53 +14032,60 @@ std::pair<uint64_t, std::vector<std::pair<crypto::key_image, crypto::signature>>
return std::make_pair(offset, ski);
}
@@ -1008,10 +999,10 @@ index 48267d647..e14d4d2fc 100644
ski.push_back(std::make_pair(key_image, signature));
}
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
-index 022d0696f..746e2aeff 100644
+index a1d939a5a..e309cec5e 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
-@@ -1157,6 +1157,7 @@ private:
+@@ -1164,6 +1164,7 @@ private:
// locked & unlocked balance of given or current subaddress account
uint64_t balance(uint32_t subaddr_index_major, bool strict) const;
uint64_t unlocked_balance(uint32_t subaddr_index_major, bool strict, uint64_t *blocks_to_unlock = NULL, uint64_t *time_to_unlock = NULL);
@@ -1019,7 +1010,7 @@ index 022d0696f..746e2aeff 100644
// locked & unlocked balance per subaddress of given or current subaddress account
std::map<uint32_t, uint64_t> balance_per_subaddress(uint32_t subaddr_index_major, bool strict) const;
std::map<uint32_t, std::pair<uint64_t, std::pair<uint64_t, uint64_t>>> unlocked_balance_per_subaddress(uint32_t subaddr_index_major, bool strict);
-@@ -1631,9 +1632,11 @@ private:
+@@ -1638,9 +1639,11 @@ private:
std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> export_blockchain() const;
void import_blockchain(const std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> &bc);
bool export_key_images(const std::string &filename, bool all = false) const;
@@ -1031,6 +1022,13 @@ index 022d0696f..746e2aeff 100644
bool import_key_images(std::vector<crypto::key_image> key_images, size_t offset=0, boost::optional<std::unordered_set<size_t>> selected_transfers=boost::none);
bool import_key_images(signed_tx_set & signed_tx, size_t offset=0, bool only_selected_transfers=false);
crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const;
+diff --git a/external/bc-ur b/external/bc-ur
+new file mode 160000
+index 000000000..d82e7c753
+--- /dev/null
++++ b/external/bc-ur
+@@ -0,0 +1 @@
++Subproject commit d82e7c753e710b8000706dc3383b498438795208
--
-2.48.0
+2.51.0
diff --git a/patches/wownero/0008-add-dummy-device-for-ledger.patch b/patches/wownero/0007-add-dummy-device-for-ledger.patch
index 47b0cc7..6f32873 100644
--- a/patches/wownero/0008-add-dummy-device-for-ledger.patch
+++ b/patches/wownero/0007-add-dummy-device-for-ledger.patch
@@ -1,10 +1,10 @@
-From 221fb7ed81aff1830b4bc2d7f9b73c94eb36924a Mon Sep 17 00:00:00 2001
+From be6bbe3958304e17566b2bc601255b0d6cec119f Mon Sep 17 00:00:00 2001
From: Czarek Nakamoto <cyjan@mrcyjanek.net>
Date: Wed, 26 Jun 2024 15:04:38 +0200
-Subject: [PATCH 08/15] add dummy device for ledger
+Subject: [PATCH 07/16] add dummy device for ledger
---
- CMakeLists.txt | 6 +-
+ CMakeLists.txt | 19 +++--
src/device/CMakeLists.txt | 6 +-
src/device/device.cpp | 10 ++-
src/device/device.hpp | 12 +--
@@ -16,15 +16,15 @@ Subject: [PATCH 08/15] add dummy device for ledger
src/wallet/api/wallet.h | 18 ++++
src/wallet/api/wallet2_api.h | 12 +++
src/wallet/api/wallet_manager.cpp | 12 ++-
- 12 files changed, 365 insertions(+), 25 deletions(-)
+ 12 files changed, 372 insertions(+), 31 deletions(-)
create mode 100644 src/device/device_io_dummy.cpp
create mode 100644 src/device/device_io_dummy.hpp
diff --git a/CMakeLists.txt b/CMakeLists.txt
-index c73b813d8..ce5ef4bab 100644
+index c439e5300..86af78f10 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
-@@ -692,16 +692,21 @@ include_directories(${LMDB_INCLUDE})
+@@ -693,16 +693,21 @@ include_directories(${LMDB_INCLUDE})
include_directories(${LIBUNWIND_INCLUDE})
link_directories(${LIBUNWIND_LIBRARY_DIRS})
@@ -84,7 +84,7 @@ index e4f1159b5..14d398f87 100644
${device_headers}
device_ledger.hpp
diff --git a/src/device/device.cpp b/src/device/device.cpp
-index e6cd358b6..777584c01 100644
+index e6cd358b6..dd0701e0c 100644
--- a/src/device/device.cpp
+++ b/src/device/device.cpp
@@ -29,7 +29,7 @@
@@ -146,10 +146,10 @@ index 392703a24..ffd419779 100644
diff --git a/src/device/device_io_dummy.cpp b/src/device/device_io_dummy.cpp
new file mode 100644
-index 000000000..edb4beea3
+index 000000000..f91e10651
--- /dev/null
+++ b/src/device/device_io_dummy.cpp
-@@ -0,0 +1,133 @@
+@@ -0,0 +1,135 @@
+// Copyright (c) 2017-2022, The Monero Project
+//
+// All rights reserved.
@@ -192,6 +192,8 @@ index 000000000..edb4beea3
+
+#if defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)
+#include <boost/scope_exit.hpp>
++#include <boost/chrono.hpp>
++#include <boost/thread.hpp>
+#include "log.hpp"
+#include "device_io_dummy.hpp"
+#include "device_ledger.hpp"
@@ -250,13 +252,13 @@ index 000000000..edb4beea3
+ MDEBUG("exchange(): waitsForDeviceSend");
+ // NOTE: waitsForDeviceSend should be changed by external code
+ while (waitsForDeviceSend) {
-+ std::this_thread::sleep_for(std::chrono::microseconds(1000));
++ boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
+ MDEBUG("exchange(): waitsForDeviceSend (still)");
+ }
+
+ MDEBUG("exchange(): waitsForDeviceReceive");
+ while (waitsForDeviceReceive) {
-+ std::this_thread::sleep_for(std::chrono::microseconds(1000));
++ boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
+ MDEBUG("exchange(): waitsForDeviceReceive (still)");
+ }
+
@@ -365,7 +367,7 @@ index 000000000..a1733616d
+
+#endif // HAVE_HIDAPI
diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp
-index 8403d76e8..0587eb7d3 100644
+index e1bfa7041..0139f1577 100644
--- a/src/device/device_ledger.cpp
+++ b/src/device/device_ledger.cpp
@@ -41,7 +41,7 @@ namespace hw {
@@ -430,7 +432,7 @@ index 03058c4f1..39454ca6d 100644
unsigned char buffer_send[BUFFER_SEND_SIZE];
unsigned int length_recv;
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
-index ee000e7ab..556e2a8ce 100644
+index e04205e48..aec76ffc0 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -48,6 +48,9 @@
@@ -615,5 +617,5 @@ index e81b8f83a..277be6ac9 100644
std::vector<std::string> WalletManagerImpl::findWallets(const std::string &path)
--
-2.48.0
+2.51.0
diff --git a/patches/wownero/0009-polyseed.patch b/patches/wownero/0008-polyseed.patch
index 6f5d5d3..aeb17b1 100644
--- a/patches/wownero/0009-polyseed.patch
+++ b/patches/wownero/0008-polyseed.patch
@@ -1,7 +1,7 @@
-From d146c6c1e23e1bf3762449e72a3aab847b3c0412 Mon Sep 17 00:00:00 2001
+From 2aec74a418dda0560e51ef2bdbfbc87ea8765bf4 Mon Sep 17 00:00:00 2001
From: tobtoht <tob@featherwallet.org>
Date: Tue, 12 Mar 2024 09:42:37 +0100
-Subject: [PATCH 09/15] polyseed
+Subject: [PATCH 08/16] polyseed
Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
---
@@ -17,8 +17,6 @@ Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
contrib/epee/include/wipeable_string.h | 7 +
contrib/epee/src/wipeable_string.cpp | 10 +
external/CMakeLists.txt | 2 +
- external/polyseed | 1 +
- external/utf8proc | 1 +
src/CMakeLists.txt | 1 +
src/cryptonote_basic/CMakeLists.txt | 1 +
src/cryptonote_basic/account.cpp | 23 ++-
@@ -36,12 +34,10 @@ Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
src/wallet/api/wallet_manager.h | 10 +
src/wallet/wallet2.cpp | 99 ++++++++--
src/wallet/wallet2.h | 30 ++-
- 31 files changed, 912 insertions(+), 23 deletions(-)
+ 29 files changed, 910 insertions(+), 23 deletions(-)
create mode 100644 contrib/depends/packages/polyseed.mk
create mode 100644 contrib/depends/patches/polyseed/0001-disable-soname.patch
create mode 100644 contrib/depends/patches/polyseed/force-static-mingw.patch
- create mode 160000 external/polyseed
- create mode 160000 external/utf8proc
create mode 100644 src/polyseed/CMakeLists.txt
create mode 100644 src/polyseed/pbkdf2.c
create mode 100644 src/polyseed/pbkdf2.h
@@ -49,10 +45,10 @@ Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
create mode 100644 src/polyseed/polyseed.hpp
diff --git a/.gitmodules b/.gitmodules
-index b24855d9b..589676649 100644
+index c1c0d385d..9edead0ee 100644
--- a/.gitmodules
+++ b/.gitmodules
-@@ -20,3 +20,9 @@
+@@ -19,3 +19,9 @@
path = external/bc-ur
url = https://github.com/MrCyjaneK/bc-ur
branch = misc
@@ -64,11 +60,11 @@ index b24855d9b..589676649 100644
+ url = https://github.com/tevador/polyseed.git
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
-index 86000f811..96056e324 100644
+index 86af78f10..0007b5ea9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -372,6 +372,8 @@ if(NOT MANUAL_SUBMODULES)
- #check_submodule(external/trezor-common)
+ check_submodule(external/trezor-common)
check_submodule(external/randomwow)
check_submodule(external/supercop)
+ check_submodule(external/polyseed)
@@ -307,32 +303,18 @@ index b016f2f48..f2f365b1b 100644
+
}
diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt
-index dfdffe67c..fa98f61b5 100644
+index d0b6d9b14..ad30abc1f 100644
--- a/external/CMakeLists.txt
+++ b/external/CMakeLists.txt
-@@ -70,4 +70,6 @@ add_subdirectory(db_drivers)
+@@ -71,4 +71,6 @@ add_subdirectory(db_drivers)
add_subdirectory(easylogging++)
add_subdirectory(qrcodegen)
add_subdirectory(bc-ur)
+add_subdirectory(polyseed EXCLUDE_FROM_ALL)
+add_subdirectory(utf8proc EXCLUDE_FROM_ALL)
add_subdirectory(randomwow EXCLUDE_FROM_ALL)
-diff --git a/external/polyseed b/external/polyseed
-new file mode 160000
-index 000000000..bd79f5014
---- /dev/null
-+++ b/external/polyseed
-@@ -0,0 +1 @@
-+Subproject commit bd79f5014c331273357277ed8a3d756fb61b9fa1
-diff --git a/external/utf8proc b/external/utf8proc
-new file mode 160000
-index 000000000..3de4596fb
---- /dev/null
-+++ b/external/utf8proc
-@@ -0,0 +1 @@
-+Subproject commit 3de4596fbe28956855df2ecb3c11c0bbc3535838
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
-index 9216bcaa5..c043ba150 100644
+index 3335d3c21..06b708cf0 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -95,6 +95,7 @@ add_subdirectory(net)
@@ -447,10 +429,10 @@ index 93d1d28f0..1f76febce 100644
const account_keys& get_keys() const;
std::string get_public_address_str(network_type nettype) const;
diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h
-index 8b5091a46..d9151e8d2 100644
+index 42861a8ff..8973d9fb8 100644
--- a/src/cryptonote_config.h
+++ b/src/cryptonote_config.h
-@@ -219,6 +219,8 @@
+@@ -223,6 +223,8 @@
#define DNS_BLOCKLIST_LIFETIME (86400 * 8)
@@ -998,7 +980,7 @@ index 000000000..2c8c777a7
+#endif //POLYSEED_HPP
\ No newline at end of file
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
-index 556e2a8ce..704e5e148 100644
+index aec76ffc0..f315b7ed6 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -728,6 +728,28 @@ bool WalletImpl::recoverFromDevice(const std::string &path, const std::string &p
@@ -1194,7 +1176,7 @@ index a223e1df9..28fcd36c9 100644
bool walletExists(const std::string &path) override;
bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key, uint64_t kdf_rounds = 1) const override;
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
-index e14d4d2fc..fd4094360 100644
+index d89595cf8..849128ca5 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -92,6 +92,7 @@ using namespace epee;
@@ -1205,17 +1187,17 @@ index e14d4d2fc..fd4094360 100644
extern "C"
{
-@@ -1278,7 +1279,8 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std
+@@ -1281,7 +1282,8 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std
m_enable_multisig(false),
m_pool_info_query_time(0),
m_has_ever_refreshed_from_node(false),
-- m_allow_mismatched_daemon_version(true)
-+ m_allow_mismatched_daemon_version(true),
+- m_allow_mismatched_daemon_version(false)
++ m_allow_mismatched_daemon_version(false),
+ m_polyseed(false)
{
set_rpc_client_secret_key(rct::rct2sk(rct::skGen()));
}
-@@ -1483,6 +1485,20 @@ bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeab
+@@ -1486,6 +1488,20 @@ bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeab
return true;
}
//----------------------------------------------------------------------------------------------------
@@ -1236,7 +1218,7 @@ index e14d4d2fc..fd4094360 100644
bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase) const
{
bool ready;
-@@ -4801,6 +4817,9 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const crypt
+@@ -4851,6 +4867,9 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const crypt
value2.SetInt(m_enable_multisig ? 1 : 0);
json.AddMember("enable_multisig", value2, json.GetAllocator());
@@ -1246,15 +1228,15 @@ index e14d4d2fc..fd4094360 100644
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());
-@@ -5040,6 +5059,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
+@@ -5090,6 +5109,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
m_enable_multisig = false;
- m_allow_mismatched_daemon_version = true;
+ m_allow_mismatched_daemon_version = false;
m_custom_background_key = boost::none;
+ m_polyseed = false;
}
else if(json.IsObject())
{
-@@ -5280,6 +5300,9 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
+@@ -5330,6 +5350,9 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, background_sync_type, BackgroundSyncType, Int, false, BackgroundSyncOff);
m_background_sync_type = field_background_sync_type;
@@ -1264,7 +1246,7 @@ index e14d4d2fc..fd4094360 100644
// Load encryption key used to encrypt background cache
crypto::chacha_key custom_background_key;
m_custom_background_key = boost::none;
-@@ -5599,6 +5622,48 @@ void wallet2::init_type(hw::device::device_type device_type)
+@@ -5649,6 +5672,48 @@ void wallet2::init_type(hw::device::device_type device_type)
m_key_device_type = device_type;
}
@@ -1313,7 +1295,7 @@ index e14d4d2fc..fd4094360 100644
/*!
* \brief Generates a wallet or restores one. Assumes the multisig setup
* has already completed for the provided multisig info.
-@@ -5726,7 +5791,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip
+@@ -5776,7 +5841,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip
return retval;
}
@@ -1322,7 +1304,7 @@ index e14d4d2fc..fd4094360 100644
{
// -1 month for fluctuations in block time and machine date/time setup.
// avg seconds per block
-@@ -5750,7 +5815,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip
+@@ -5800,7 +5865,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip
// the daemon is currently syncing.
// If we use the approximate height we subtract one month as
// a safety margin.
@@ -1331,7 +1313,7 @@ index e14d4d2fc..fd4094360 100644
uint64_t target_height = get_daemon_blockchain_target_height(err);
if (err.empty()) {
if (target_height < height)
-@@ -13643,9 +13708,10 @@ uint64_t wallet2::get_daemon_blockchain_target_height(string &err)
+@@ -13634,9 +13699,10 @@ uint64_t wallet2::get_daemon_blockchain_target_height(string &err)
return target_height;
}
@@ -1343,7 +1325,7 @@ index e14d4d2fc..fd4094360 100644
LOG_PRINT_L2("Calculated blockchain height: " << approx_blockchain_height);
return approx_blockchain_height;
}
-@@ -15780,15 +15846,6 @@ bool wallet2::parse_uri(const std::string &uri, std::string &address, std::strin
+@@ -15758,15 +15824,6 @@ bool wallet2::parse_uri(const std::string &uri, std::string &address, std::strin
//----------------------------------------------------------------------------------------------------
uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, uint8_t day)
{
@@ -1359,7 +1341,7 @@ index e14d4d2fc..fd4094360 100644
std::tm date = { 0, 0, 0, 0, 0, 0, 0, 0 };
date.tm_year = year - 1900;
date.tm_mon = month - 1;
-@@ -15797,7 +15854,23 @@ uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, ui
+@@ -15775,7 +15832,23 @@ uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, ui
{
throw std::runtime_error("month or day out of range");
}
@@ -1384,7 +1366,7 @@ index e14d4d2fc..fd4094360 100644
uint64_t height_min = 0;
uint64_t height_max = get_daemon_blockchain_height(err) - 1;
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
-index 746e2aeff..c165acb9d 100644
+index e309cec5e..9c520fa99 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -72,6 +72,7 @@
@@ -1432,7 +1414,7 @@ index 746e2aeff..c165acb9d 100644
/*!
* \brief Checks if light wallet. A light wallet sends view key to a server where the blockchain is scanned.
*/
-@@ -1562,8 +1586,8 @@ private:
+@@ -1569,8 +1593,8 @@ private:
/*!
* \brief Calculates the approximate blockchain height from current date/time.
*/
@@ -1443,7 +1425,7 @@ index 746e2aeff..c165acb9d 100644
std::vector<size_t> select_available_outputs_from_histogram(uint64_t count, bool atleast, bool unlocked, bool allow_rct);
std::vector<size_t> select_available_outputs(const std::function<bool(const transfer_details &td)> &f);
std::vector<size_t> select_available_unmixable_outputs();
-@@ -1657,6 +1681,7 @@ private:
+@@ -1664,6 +1688,7 @@ private:
bool 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);
uint64_t get_blockchain_height_by_date(uint16_t year, uint8_t month, uint8_t day); // 1<=month<=12, 1<=day<=31
@@ -1451,7 +1433,7 @@ index 746e2aeff..c165acb9d 100644
bool is_synced();
-@@ -2003,6 +2028,7 @@ private:
+@@ -2010,6 +2035,7 @@ private:
std::string seed_language; /*!< Language of the mnemonics (seed). */
bool is_old_file_format; /*!< Whether the wallet file is of an old file format */
bool m_watch_only; /*!< no spend key */
@@ -1459,6 +1441,21 @@ index 746e2aeff..c165acb9d 100644
bool m_multisig; /*!< if > 1 spend secret key will not match spend public key */
uint32_t m_multisig_threshold;
std::vector<crypto::public_key> m_multisig_signers;
+
+diff --git a/external/polyseed b/external/polyseed
+new file mode 160000
+index 000000000..bd79f5014
+--- /dev/null
++++ b/external/polyseed
+@@ -0,0 +1 @@
++Subproject commit bd79f5014c331273357277ed8a3d756fb61b9fa1
+diff --git a/external/utf8proc b/external/utf8proc
+new file mode 160000
+index 000000000..3de4596fb
+--- /dev/null
++++ b/external/utf8proc
+@@ -0,0 +1 @@
++Subproject commit 3de4596fbe28956855df2ecb3c11c0bbc3535838
--
-2.48.0
+2.51.0
diff --git a/patches/wownero/0010-coin-control.patch b/patches/wownero/0009-coin-control.patch
index 94f8485..d3dd5e0 100644
--- a/patches/wownero/0010-coin-control.patch
+++ b/patches/wownero/0009-coin-control.patch
@@ -1,7 +1,7 @@
-From edc33fa98da3bc9e8e746a59f5e62b9001afb230 Mon Sep 17 00:00:00 2001
+From 8b680843825e374cb21d2ea7895442d5436cd12d Mon Sep 17 00:00:00 2001
From: tobtoht <tob@featherwallet.org>
Date: Tue, 12 Mar 2024 11:07:57 +0100
-Subject: [PATCH 10/15] coin control
+Subject: [PATCH 09/16] coin control
---
src/simplewallet/simplewallet.cpp | 2 +-
@@ -22,10 +22,10 @@ Subject: [PATCH 10/15] coin control
create mode 100644 src/wallet/api/coins_info.h
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
-index 83b56c3f4..12c38b8e1 100644
+index 42e45054d..58f60783e 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
-@@ -6981,7 +6981,7 @@ bool simple_wallet::transfer_main(const std::vector<std::string> &args_, bool ca
+@@ -6971,7 +6971,7 @@ bool simple_wallet::transfer_main(const std::vector<std::string> &args_, bool ca
{
// figure out what tx will be necessary
auto ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, priority, extra,
@@ -504,7 +504,7 @@ index 000000000..c43e45abd
+
+#endif //FEATHER_COINS_INFO_H
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
-index 704e5e148..e69910e69 100644
+index f315b7ed6..7066fc3d8 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -35,6 +35,7 @@
@@ -627,7 +627,7 @@ index 704e5e148..e69910e69 100644
}
PendingTransaction *WalletImpl::createSweepUnmixableTransaction()
-@@ -2342,6 +2391,11 @@ AddressBook *WalletImpl::addressBook()
+@@ -2343,6 +2392,11 @@ AddressBook *WalletImpl::addressBook()
return m_addressBook.get();
}
@@ -777,10 +777,10 @@ index be1c3704e..013b5bcba 100644
virtual SubaddressAccount * subaddressAccount() = 0;
virtual void setListener(WalletListener *) = 0;
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
-index fd4094360..be3096675 100644
+index 849128ca5..f96de960a 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
-@@ -2103,12 +2103,21 @@ bool wallet2::frozen(const multisig_tx_set& txs) const
+@@ -2136,12 +2136,21 @@ bool wallet2::frozen(const multisig_tx_set& txs) const
return false;
}
@@ -802,7 +802,7 @@ index fd4094360..be3096675 100644
void wallet2::thaw(const crypto::key_image &ki)
{
thaw(get_transfer_details(ki));
-@@ -2119,6 +2128,18 @@ bool wallet2::frozen(const crypto::key_image &ki) const
+@@ -2152,6 +2161,18 @@ bool wallet2::frozen(const crypto::key_image &ki) const
return frozen(get_transfer_details(ki));
}
//----------------------------------------------------------------------------------------------------
@@ -821,7 +821,7 @@ index fd4094360..be3096675 100644
size_t wallet2::get_transfer_details(const crypto::key_image &ki) const
{
for (size_t idx = 0; idx < m_transfers.size(); ++idx)
-@@ -2532,6 +2553,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
+@@ -2566,6 +2587,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
uint64_t amount = tx.vout[o].amount ? tx.vout[o].amount : tx_scan_info[o].amount;
if (!pool)
{
@@ -829,7 +829,7 @@ index fd4094360..be3096675 100644
m_transfers.push_back(transfer_details{});
transfer_details& td = m_transfers.back();
td.m_block_height = height;
-@@ -2635,6 +2657,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
+@@ -2669,6 +2691,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
uint64_t extra_amount = amount - burnt;
if (!pool)
{
@@ -837,7 +837,7 @@ index fd4094360..be3096675 100644
transfer_details &td = m_transfers[kit->second];
td.m_block_height = height;
td.m_internal_output_index = o;
-@@ -10506,7 +10529,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
+@@ -10494,7 +10517,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
LOG_PRINT_L2("transfer_selected_rct done");
}
@@ -846,7 +846,7 @@ index fd4094360..be3096675 100644
{
std::vector<size_t> picks;
float current_output_relatdness = 1.0f;
-@@ -10517,6 +10540,9 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui
+@@ -10505,6 +10528,9 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
@@ -856,7 +856,7 @@ index fd4094360..be3096675 100644
if (!is_spent(td, false) && !td.m_frozen && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
{
if (td.amount() > m_ignore_outputs_above || td.amount() < m_ignore_outputs_below)
-@@ -10537,6 +10563,9 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui
+@@ -10525,6 +10551,9 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
@@ -866,7 +866,7 @@ index fd4094360..be3096675 100644
if (!is_spent(td, false) && !td.m_frozen && !td.m_key_image_partial && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
{
if (td.amount() > m_ignore_outputs_above || td.amount() < m_ignore_outputs_below)
-@@ -10548,6 +10577,9 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui
+@@ -10536,6 +10565,9 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui
for (size_t j = i + 1; j < m_transfers.size(); ++j)
{
const transfer_details& td2 = m_transfers[j];
@@ -876,7 +876,7 @@ index fd4094360..be3096675 100644
if (td2.amount() > m_ignore_outputs_above || td2.amount() < m_ignore_outputs_below)
{
MDEBUG("Ignoring output " << j << " of amount " << print_money(td2.amount()) << " which is outside prescribed range [" << print_money(m_ignore_outputs_below) << ", " << print_money(m_ignore_outputs_above) << "]");
-@@ -11120,7 +11152,7 @@ bool wallet2::light_wallet_key_image_is_ours(const crypto::key_image& key_image,
+@@ -11108,7 +11140,7 @@ bool wallet2::light_wallet_key_image_is_ours(const crypto::key_image& key_image,
// This system allows for sending (almost) the entire balance, since it does
// not generate spurious change in all txes, thus decreasing the instantaneous
// usable balance.
@@ -885,7 +885,7 @@ index fd4094360..be3096675 100644
{
//ensure device is let in NONE mode in any case
hw::device &hwdev = m_account.get_device();
-@@ -11328,6 +11360,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
+@@ -11317,6 +11349,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
@@ -895,16 +895,16 @@ index fd4094360..be3096675 100644
if (m_ignore_fractional_outputs && td.amount() < fractional_threshold)
{
MDEBUG("Ignoring output " << i << " of amount " << print_money(td.amount()) << " which is below fractional threshold " << print_money(fractional_threshold));
-@@ -11419,7 +11454,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
+@@ -11408,7 +11443,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// will get us a known fee.
- uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags, base_fee, fee_quantization_mask);
+ uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus, bulletproof_plus_full_commit, use_view_tags, base_fee, fee_quantization_mask);
total_needed_money = needed_money + (subtract_fee_from_outputs.size() ? 0 : estimated_fee);
- preferred_inputs = pick_preferred_rct_inputs(total_needed_money, subaddr_account, subaddr_indices);
+ preferred_inputs = pick_preferred_rct_inputs(total_needed_money, subaddr_account, subaddr_indices, preferred_input_list);
if (!preferred_inputs.empty())
{
string s;
-@@ -11898,7 +11933,7 @@ bool wallet2::sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, c
+@@ -11887,7 +11922,7 @@ bool wallet2::sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, c
return true;
}
@@ -913,7 +913,7 @@ index fd4094360..be3096675 100644
{
std::vector<size_t> unused_transfers_indices;
std::vector<size_t> unused_dust_indices;
-@@ -11927,6 +11962,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below
+@@ -11917,6 +11952,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
@@ -924,10 +924,10 @@ index fd4094360..be3096675 100644
{
MDEBUG("Ignoring output " << i << " of amount " << print_money(td.amount()) << " which is below threshold " << print_money(fractional_threshold));
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
-index c165acb9d..6b103d9c2 100644
+index 9c520fa99..66eea3ec9 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
-@@ -1216,8 +1216,8 @@ private:
+@@ -1223,8 +1223,8 @@ private:
bool parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsigned_tx_set &exported_txs) const;
bool load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func = NULL);
bool parse_tx_from_str(const std::string &signed_tx_st, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set &)> accept_func);
@@ -938,7 +938,7 @@ index c165acb9d..6b103d9c2 100644
std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra);
std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra);
bool sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, const std::vector<cryptonote::tx_destination_entry>& dsts, const unique_index_container& subtract_fee_from_outputs = {}) const;
-@@ -1569,6 +1569,7 @@ private:
+@@ -1576,6 +1576,7 @@ private:
uint64_t get_num_rct_outputs();
size_t get_num_transfer_details() const { return m_transfers.size(); }
const transfer_details &get_transfer_details(size_t idx) const;
@@ -946,7 +946,7 @@ index c165acb9d..6b103d9c2 100644
uint8_t get_current_hard_fork();
void get_hard_fork_info(uint8_t version, uint64_t &earliest_height);
-@@ -1800,7 +1801,9 @@ private:
+@@ -1807,7 +1808,9 @@ private:
void freeze(size_t idx);
void thaw(size_t idx);
bool frozen(size_t idx) const;
@@ -956,7 +956,7 @@ index c165acb9d..6b103d9c2 100644
void thaw(const crypto::key_image &ki);
bool frozen(const crypto::key_image &ki) const;
bool frozen(const transfer_details &td) const;
-@@ -1841,6 +1844,8 @@ private:
+@@ -1848,6 +1851,8 @@ private:
static std::string get_default_daemon_address() { CRITICAL_REGION_LOCAL(default_daemon_address_lock); return default_daemon_address; }
@@ -965,7 +965,7 @@ index c165acb9d..6b103d9c2 100644
private:
/*!
* \brief Stores wallet information to wallet file.
-@@ -1912,7 +1917,7 @@ private:
+@@ -1919,7 +1924,7 @@ private:
std::vector<uint64_t> get_unspent_amounts_vector(bool strict);
uint64_t get_dynamic_base_fee_estimate();
float get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const;
@@ -975,5 +975,5 @@ index c165acb9d..6b103d9c2 100644
void set_unspent(size_t idx);
bool is_spent(const transfer_details &td, bool strict = true) const;
--
-2.48.0
+2.51.0
diff --git a/patches/wownero/0011-Add-hex-encoding-and-tx-key-getter-for-PendingTransc.patch b/patches/wownero/0010-Add-hex-encoding-and-tx-key-getter-for-PendingTransc.patch
index de19446..62fc6ef 100644
--- a/patches/wownero/0011-Add-hex-encoding-and-tx-key-getter-for-PendingTransc.patch
+++ b/patches/wownero/0010-Add-hex-encoding-and-tx-key-getter-for-PendingTransc.patch
@@ -1,7 +1,7 @@
-From 96911b156ad9cfef2ebf2809a393492e87ab7607 Mon Sep 17 00:00:00 2001
+From 32d3c34073563f34ce90a8afdbe8e554e98d38bc Mon Sep 17 00:00:00 2001
From: M <m@cakewallet.com>
Date: Fri, 21 Apr 2023 15:43:47 -0400
-Subject: [PATCH 11/15] Add hex encoding and tx key getter for
+Subject: [PATCH 10/16] Add hex encoding and tx key getter for
PendingTransction in wallet api.
---
@@ -64,5 +64,5 @@ index 013b5bcba..f421fdc05 100644
/**
--
-2.48.0
+2.51.0
diff --git a/patches/wownero/0012-Add-recoverDeterministicWalletFromSpendKey.patch b/patches/wownero/0011-Add-recoverDeterministicWalletFromSpendKey.patch
index bbb5547..a6cf6f5 100644
--- a/patches/wownero/0012-Add-recoverDeterministicWalletFromSpendKey.patch
+++ b/patches/wownero/0011-Add-recoverDeterministicWalletFromSpendKey.patch
@@ -1,7 +1,7 @@
-From ad28016bf3cfd11242dc14472389f7a006c73dcf Mon Sep 17 00:00:00 2001
+From 9f1a4324d0346d3fc9baf055f0d86f796d1dcecd Mon Sep 17 00:00:00 2001
From: Konstantin Ullrich <konstantinullrich12@gmail.com>
Date: Wed, 11 Oct 2023 16:47:59 +0200
-Subject: [PATCH 12/15] Add recoverDeterministicWalletFromSpendKey
+Subject: [PATCH 11/16] Add recoverDeterministicWalletFromSpendKey
This function is used by Cake Wallet to enable polyseed (dart implementation)
support.
@@ -19,7 +19,7 @@ Co-authored-by: Godwin Asuquo <godilite@gmail.com>
5 files changed, 75 insertions(+)
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
-index e69910e69..e650e6044 100644
+index 7066fc3d8..690fbdce1 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -824,6 +824,35 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c
@@ -149,5 +149,5 @@ index 28fcd36c9..be3ff8184 100644
const std::string &password,
NetworkType nettype,
--
-2.48.0
+2.51.0
diff --git a/patches/wownero/0013-add-monero-submodule-support.patch b/patches/wownero/0012-add-monero-submodule-support.patch
index 6b1e987..6380cd2 100644
--- a/patches/wownero/0013-add-monero-submodule-support.patch
+++ b/patches/wownero/0012-add-monero-submodule-support.patch
@@ -1,20 +1,21 @@
-From f8b3055a79c929dc167d20d4874ab20b0a51d49d Mon Sep 17 00:00:00 2001
+From 733d8fa11a455ff144d29a6a5fad384212d1931a Mon Sep 17 00:00:00 2001
From: cyan <cyjan@mrcyjanek.net>
Date: Thu, 7 Nov 2024 16:46:24 +0000
-Subject: [PATCH 13/15] add monero submodule support
+Subject: [PATCH 12/16] add monero submodule support
---
CMakeLists.txt | 6 +++---
cmake/CheckLinkerFlag.cmake | 2 +-
+ cmake/FindCcache.cmake | 2 +-
src/wallet/wallet_rpc_server.cpp | 2 +-
- 3 files changed, 5 insertions(+), 5 deletions(-)
+ 4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
-index 96056e324..1383e630a 100644
+index 0007b5ea9..caa1d5e33 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -223,9 +223,9 @@ function(forbid_undefined_symbols)
- cmake_minimum_required(VERSION 3.1)
+ cmake_minimum_required(VERSION 3.5)
project(test)
option(EXPECT_SUCCESS "" ON)
-file(WRITE "${CMAKE_SOURCE_DIR}/incorrect_source.cpp" "void undefined_symbol(); void symbol() { undefined_symbol(); }")
@@ -35,23 +36,36 @@ index 96056e324..1383e630a 100644
if (NOT DEFINED ENV{DEVELOPER_LOCAL_TOOLS})
message(STATUS "Could not find DEVELOPER_LOCAL_TOOLS in env (not required)")
diff --git a/cmake/CheckLinkerFlag.cmake b/cmake/CheckLinkerFlag.cmake
-index 7ecf5f610..89fb9d167 100644
+index 69eefe810..89fb9d167 100644
--- a/cmake/CheckLinkerFlag.cmake
+++ b/cmake/CheckLinkerFlag.cmake
@@ -6,7 +6,7 @@ macro(CHECK_LINKER_FLAG flag VARIABLE)
message(STATUS "Looking for ${flag} linker flag")
endif()
-- set(_cle_source ${CMAKE_SOURCE_DIR}/cmake/CheckLinkerFlag.c)
+- set(_cle_source ${monero_SOURCE_DIR}/cmake/CheckLinkerFlag.c)
+ set(_cle_source ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CheckLinkerFlag.c)
set(saved_CMAKE_C_FLAGS ${CMAKE_C_FLAGS})
set(CMAKE_C_FLAGS "${flag}")
+diff --git a/cmake/FindCcache.cmake b/cmake/FindCcache.cmake
+index d3f5f829b..68e4851cb 100644
+--- a/cmake/FindCcache.cmake
++++ b/cmake/FindCcache.cmake
+@@ -47,7 +47,7 @@ if (CCACHE_FOUND)
+ cmake_minimum_required(VERSION 3.5)
+ project(test)
+ option (CCACHE "")
+-file(WRITE "${CMAKE_SOURCE_DIR}/test.cpp" "int main() { return 0; }")
++file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/test.cpp" "int main() { return 0; }")
+ set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE}")
+ set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CCACHE}")
+ add_executable(main test.cpp)
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
-index 21e69ea0e..d9bc5dda3 100644
+index d4167e8cb..f7a07ca26 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
-@@ -1162,7 +1162,7 @@ namespace tools
+@@ -1261,7 +1261,7 @@ namespace tools
{
uint64_t mixin = m_wallet->adjust_mixin(req.ring_size ? req.ring_size - 1 : 0);
uint32_t priority = m_wallet->adjust_priority(req.priority);
@@ -61,5 +75,5 @@ index 21e69ea0e..d9bc5dda3 100644
if (ptx_vector.empty())
{
--
-2.48.0
+2.51.0
diff --git a/patches/wownero/0014-fix-iOS-depends-build.patch b/patches/wownero/0013-fix-iOS-depends-build.patch
index 5cb1c79..78464b7 100644
--- a/patches/wownero/0014-fix-iOS-depends-build.patch
+++ b/patches/wownero/0013-fix-iOS-depends-build.patch
@@ -1,7 +1,7 @@
-From a707d5fc2aa9d387857381e7ebc4a68c1d245a00 Mon Sep 17 00:00:00 2001
+From 23507763ad4a3b2461b97ef0a993d6a06158312d Mon Sep 17 00:00:00 2001
From: Czarek Nakamoto <cyjan@mrcyjanek.net>
Date: Thu, 21 Nov 2024 06:05:03 -0500
-Subject: [PATCH 14/15] fix iOS depends build
+Subject: [PATCH 13/16] fix iOS depends build
---
CMakeLists.txt | 4 ----
@@ -11,7 +11,7 @@ Subject: [PATCH 14/15] fix iOS depends build
4 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
-index 1383e630a..b8782570d 100644
+index caa1d5e33..59d598edb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -39,10 +39,6 @@ include(CheckLibraryExists)
@@ -60,7 +60,7 @@ index 414936a05..81c81767f 100644
find_library(IOKIT_LIBRARY IOKit)
mark_as_advanced(IOKIT_LIBRARY)
diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp
-index 83bea8b5b..dfb4b5c5a 100644
+index 70e95d398..18563c3b3 100644
--- a/src/cryptonote_basic/miner.cpp
+++ b/src/cryptonote_basic/miner.cpp
@@ -45,7 +45,7 @@
@@ -72,7 +72,7 @@ index 83bea8b5b..dfb4b5c5a 100644
#include <sys/times.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/ps/IOPSKeys.h>
-@@ -971,7 +971,7 @@ namespace cryptonote
+@@ -973,7 +973,7 @@ namespace cryptonote
return true;
@@ -81,7 +81,7 @@ index 83bea8b5b..dfb4b5c5a 100644
mach_msg_type_number_t count;
kern_return_t status;
-@@ -1037,7 +1037,7 @@ namespace cryptonote
+@@ -1039,7 +1039,7 @@ namespace cryptonote
return true;
}
@@ -90,7 +90,7 @@ index 83bea8b5b..dfb4b5c5a 100644
struct tms tms;
if ( times(&tms) != (clock_t)-1 )
-@@ -1066,7 +1066,7 @@ namespace cryptonote
+@@ -1068,7 +1068,7 @@ namespace cryptonote
return boost::logic::tribool(power_status.ACLineStatus != 1);
}
@@ -100,5 +100,5 @@ index 83bea8b5b..dfb4b5c5a 100644
#if TARGET_OS_MAC && (!defined(MAC_OS_X_VERSION_MIN_REQUIRED) || MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7)
return boost::logic::tribool(IOPSGetTimeRemainingEstimate() != kIOPSTimeRemainingUnlimited);
--
-2.48.0
+2.51.0
diff --git a/patches/wownero/0015-include-locale-only-when-targeting-WIN32.patch b/patches/wownero/0014-include-locale-only-when-targeting-WIN32.patch
index 27ec9bc..913ee93 100644
--- a/patches/wownero/0015-include-locale-only-when-targeting-WIN32.patch
+++ b/patches/wownero/0014-include-locale-only-when-targeting-WIN32.patch
@@ -1,7 +1,7 @@
-From eeb059ab8bff7e8614531565259ca66f6e6a8c7d Mon Sep 17 00:00:00 2001
+From 54c9d7a6a6feb0196e638511dabc50e119038ca7 Mon Sep 17 00:00:00 2001
From: Czarek Nakamoto <cyjan@mrcyjanek.net>
Date: Mon, 18 Nov 2024 10:57:37 -0500
-Subject: [PATCH 15/15] include locale only when targeting WIN32
+Subject: [PATCH 14/16] include locale only when targeting WIN32
---
CMakeLists.txt | 5 ++++-
@@ -9,10 +9,10 @@ Subject: [PATCH 15/15] include locale only when targeting WIN32
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
-index b8782570d..d1ed1de7b 100644
+index 59d598edb..a620e8a37 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
-@@ -1090,7 +1090,10 @@ if(NOT Boost_FOUND)
+@@ -1091,7 +1091,10 @@ if(NOT Boost_FOUND)
elseif(Boost_FOUND)
message(STATUS "Found Boost Version: ${Boost_VERSION_STRING}")
@@ -25,7 +25,7 @@ index b8782570d..d1ed1de7b 100644
# Boost System is header-only since 1.69
if (Boost_VERSION_STRING VERSION_LESS 1.69.0)
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
-index e650e6044..6d7553a1d 100644
+index 690fbdce1..161bd33ef 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -46,7 +46,9 @@
@@ -39,5 +39,5 @@ index e650e6044..6d7553a1d 100644
#include "bc-ur/src/bc-ur.hpp"
#if defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)
--
-2.48.0
+2.51.0
diff --git a/patches/wownero/0016-change-earliest-fork-height-message.patch b/patches/wownero/0015-change-earliest-fork-height-message.patch
index 0a0bd2c..a8daff4 100644
--- a/patches/wownero/0016-change-earliest-fork-height-message.patch
+++ b/patches/wownero/0015-change-earliest-fork-height-message.patch
@@ -1,17 +1,17 @@
-From d47284456194c6d99698b28908d753acf1a64010 Mon Sep 17 00:00:00 2001
+From 1d52a04342d63795de45958d41477047e15f0ac2 Mon Sep 17 00:00:00 2001
From: Czarek Nakamoto <cyjan@mrcyjanek.net>
Date: Wed, 29 Jan 2025 16:13:28 +0100
-Subject: [PATCH] change earliest fork height message
+Subject: [PATCH 15/16] change earliest fork height message
---
src/wallet/wallet2.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
-index d060bf95b..9c1574c02 100644
+index f96de960a..888f862f7 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
-@@ -12354,7 +12354,7 @@ bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks)
+@@ -12351,7 +12351,7 @@ bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks)
boost::optional<std::string> result = m_node_rpc_proxy.get_height(height);
THROW_WALLET_EXCEPTION_IF(result, error::wallet_internal_error, "Failed to get height");
result = m_node_rpc_proxy.get_earliest_height(version, earliest_height);
@@ -21,5 +21,5 @@ index d060bf95b..9c1574c02 100644
bool close_enough = (int64_t)height >= (int64_t)earliest_height - early_blocks && earliest_height != std::numeric_limits<uint64_t>::max(); // start using the rules that many blocks beforehand
if (close_enough)
--
-2.48.1
+2.51.0
diff --git a/patches/wownero/0016-drop-generate_translations_header.c-requirement.patch b/patches/wownero/0016-drop-generate_translations_header.c-requirement.patch
new file mode 100644
index 0000000..5b14436
--- /dev/null
+++ b/patches/wownero/0016-drop-generate_translations_header.c-requirement.patch
@@ -0,0 +1,132 @@
+From f7ccb8ea7471edb2c5eed8d29b39f7112da7a450 Mon Sep 17 00:00:00 2001
+From: Czarek Nakamoto <cyjan@mrcyjanek.net>
+Date: Fri, 20 Feb 2026 08:03:01 +0100
+Subject: [PATCH 16/16] drop generate_translations_header.c requirement
+
+---
+ CMakeLists.txt | 11 ++----
+ translations/CMakeLists.txt | 79 +++++++++++++------------------------
+ 2 files changed, 31 insertions(+), 59 deletions(-)
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index a620e8a37..6343a3066 100644
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -71,7 +71,7 @@ if (${CMAKE_VERSION} VERSION_GREATER "3.0.0" AND CMAKE_MAKE_PROGRAM MATCHES "nin
+ set(CMAKE_JOB_POOL_LINK link_job_pool)
+ endif ()
+ endif ()
+-
++
+ option (USE_CLANG_TIDY_C "Lint the code with clang-tidy - variant C" OFF)
+ option (USE_CLANG_TIDY_CXX "Lint the code with clang-tidy - variant C++" OFF)
+ if (USE_CLANG_TIDY_C AND USE_CLANG_TIDY_CXX)
+@@ -667,16 +667,11 @@ endfunction ()
+ # Generate header for embedded translations
+ # Generate header for embedded translations, use target toolchain if depends, otherwise use the
+ # lrelease and lupdate binaries from the host
+-include(ExternalProject)
+-ExternalProject_Add(generate_translations_header
+- SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/translations"
+- BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/translations"
+- STAMP_DIR ${LRELEASE_PATH}
+- CMAKE_ARGS -DLRELEASE_PATH=${LRELEASE_PATH}
+- INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "")
++add_subdirectory(translations)
+ include_directories("${CMAKE_CURRENT_BINARY_DIR}/translations")
+ add_subdirectory(external)
+
++
+ # Final setup for libunbound
+ include_directories(${UNBOUND_INCLUDE_DIR})
+
+diff --git a/translations/CMakeLists.txt b/translations/CMakeLists.txt
+index 3b43360f8..d88a78ced 100644
+--- a/translations/CMakeLists.txt
++++ b/translations/CMakeLists.txt
+@@ -30,54 +30,31 @@ cmake_minimum_required(VERSION 3.5)
+
+ project(translations)
+
+-# when crosscompiling import the executable targets from a file
+-IF(CMAKE_CROSSCOMPILING)
+- message(WARNING "CrossCompiling")
+- SET(IMPORT_EXECUTABLES "${CMAKE_CURRENT_BINARY_DIR}/ImportExecutables.cmake" CACHE FILEPATH "Point it to the export file from a native build")
+- INCLUDE(${IMPORT_EXECUTABLES})
+-ENDIF(CMAKE_CROSSCOMPILING)
+-
+-# only build the generator if not crosscompiling
+-IF(NOT CMAKE_CROSSCOMPILING)
+- add_executable(generate_translations_header generate_translations_header.c)
+-ENDIF(NOT CMAKE_CROSSCOMPILING)
+-
+-if(LRELEASE_PATH STREQUAL "")
+- find_program(LRELEASE lrelease)
+-else()
+- set(LRELEASE ${LRELEASE_PATH}/lrelease)
+-endif()
+-
+-if(LRELEASE STREQUAL "LRELEASE-NOTFOUND")
+- set(ts_files "")
+- message(WARNING "lrelease program not found, translation files not built")
+-else()
+- execute_process(COMMAND ${LRELEASE} -version
+- RESULT_VARIABLE lrelease_ret)
+- if(NOT lrelease_ret EQUAL "0")
+- set(ts_files "")
+- message(WARNING "lrelease program not working, translation files not built")
+- else()
+- file(GLOB ts_files RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" *.ts)
+- foreach(ts_file ${ts_files})
+- string(REPLACE ".ts" ".qm" qm_file "${ts_file}")
+- add_custom_command(TARGET generate_translations_header
+- PRE_BUILD
+- COMMAND ${LRELEASE} "${CMAKE_CURRENT_SOURCE_DIR}/${ts_file}" -qm "${qm_file}"
+- WORKING_DIRECTORY "${CMAKE_CURRENT_BIN_DIR}")
+- endforeach()
+- endif()
+-endif()
+-
+-string(REPLACE ".ts" ".qm" qm_files "${ts_files}")
+-
+-add_custom_command(TARGET generate_translations_header
+- POST_BUILD
+- COMMAND $<TARGET_FILE:generate_translations_header> ${qm_files}
+- WORKING_DIRECTORY "${CMAKE_CURRENT_BIN_DIR}"
+- COMMENT "Generating embedded translations header")
+-
+-# export the generator target to a file, so it can be imported (see above) by another build
+-IF(NOT CMAKE_CROSSCOMPILING)
+- EXPORT(TARGETS generate_translations_header FILE ${CMAKE_CURRENT_BINARY_DIR}/ImportExecutables.cmake )
+-ENDIF(NOT CMAKE_CROSSCOMPILING)
++add_custom_target(generate_translations_header)
++
++file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/translation_files.h"
++"#ifndef TRANSLATION_FILES_H
++#define TRANSLATION_FILES_H
++
++#include <string>
++
++static const struct embedded_file {
++ const std::string *name;
++ const std::string *data;
++} embedded_files[] = {
++ {NULL, NULL}
++};
++
++static bool find_embedded_file(const std::string &name, std::string &data) {
++ const struct embedded_file *p;
++ for (p = embedded_files; p->name != NULL; p++) {
++ if (*p->name == name) {
++ data = *p->data;
++ return true;
++ }
++ }
++ return false;
++}
++
++#endif /* TRANSLATION_FILES_H */
++")
+--
+2.51.0
+
diff --git a/patches/wownero/0017-pr-9880.patch b/patches/wownero/0017-pr-9880.patch
deleted file mode 100644
index dbf604f..0000000
--- a/patches/wownero/0017-pr-9880.patch
+++ /dev/null
@@ -1,25 +0,0 @@
-From 31ef09596a9d8d547905577823ff52d33e10a3d2 Mon Sep 17 00:00:00 2001
-From: Czarek Nakamoto <cyjan@mrcyjanek.net>
-Date: Tue, 1 Apr 2025 11:30:45 +0200
-Subject: [PATCH] pr-9880
-
----
- CMakeLists.txt | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/CMakeLists.txt b/CMakeLists.txt
-index 5938be6..1c47285 100644
---- a/CMakeLists.txt
-+++ b/CMakeLists.txt
-@@ -216,7 +216,7 @@ function(forbid_undefined_symbols)
- file(MAKE_DIRECTORY "${TEST_PROJECT}")
- file(WRITE "${TEST_PROJECT}/CMakeLists.txt"
- [=[
--cmake_minimum_required(VERSION 3.1)
-+cmake_minimum_required(VERSION 3.5)
- project(test)
- option(EXPECT_SUCCESS "" ON)
- file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/incorrect_source.cpp" "void undefined_symbol(); void symbol() { undefined_symbol(); }")
---
-2.49.0
-
diff --git a/patches/wownero/0018-fix-unary_function-__unary_function.patch b/patches/wownero/0018-fix-unary_function-__unary_function.patch
deleted file mode 100644
index 5fc385c..0000000
--- a/patches/wownero/0018-fix-unary_function-__unary_function.patch
+++ /dev/null
@@ -1,28 +0,0 @@
-From e488eaa7397d388cee6e914e10d23790f005f6f7 Mon Sep 17 00:00:00 2001
-From: Czarek Nakamoto <cyjan@mrcyjanek.net>
-Date: Thu, 10 Apr 2025 13:28:06 +0200
-Subject: [PATCH] fix: unary_function -> __unary_function
-
----
- src/cryptonote_basic/cryptonote_basic_impl.h | 4 ++++
- 1 file changed, 4 insertions(+)
-
-diff --git a/src/cryptonote_basic/cryptonote_basic_impl.h b/src/cryptonote_basic/cryptonote_basic_impl.h
-index b423573..a9aef7a 100644
---- a/src/cryptonote_basic/cryptonote_basic_impl.h
-+++ b/src/cryptonote_basic/cryptonote_basic_impl.h
-@@ -40,7 +40,11 @@ namespace cryptonote {
- /* */
- /************************************************************************/
- template<class t_array>
-+#ifdef __APPLE__
-+ struct array_hasher: std::__unary_function<t_array&, std::size_t>
-+#else
- struct array_hasher: std::unary_function<t_array&, std::size_t>
-+#endif
- {
- std::size_t operator()(const t_array& val) const
- {
---
-2.49.0
-
diff --git a/patches/wownero/0022-fix-remove-flaky-test.patch b/patches/wownero/0022-fix-remove-flaky-test.patch
new file mode 100644
index 0000000..4c70ca3
--- /dev/null
+++ b/patches/wownero/0022-fix-remove-flaky-test.patch
@@ -0,0 +1,27 @@
+From 3855cc39761321650484f5fba70c48dd8eb18444 Mon Sep 17 00:00:00 2001
+From: Czarek Nakamoto <cyjan@mrcyjanek.net>
+Date: Thu, 5 Mar 2026 18:12:53 +0100
+Subject: [PATCH] fix: remove flaky test
+
+---
+ CMakeLists.txt | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index 2525cf8ba..42189c0fe 100644
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -235,9 +235,7 @@ add_library(l3 OBJECT incorrect_source.cpp)
+ "-DCMAKE_MODULE_LINKER_FLAGS=${CMAKE_MODULE_LINKER_FLAGS}"
+ "-DEXPECT_SUCCESS=${EXPECT}"
+ )
+- if (NOT ${SUCCESS} STREQUAL ${EXPECT})
+- message(FATAL_ERROR "Undefined symbols test failure: expect(${EXPECT}), success(${SUCCESS})")
+- endif()
++
+ file(REMOVE_RECURSE "${TEST_PROJECT}")
+ endforeach()
+ endfunction()
+--
+2.51.0
+
diff --git a/patches/wownero/0025-depends-remove-icu4c-monero-project-monero-8880.patch b/patches/wownero/0025-depends-remove-icu4c-monero-project-monero-8880.patch
new file mode 100644
index 0000000..aaf2e74
--- /dev/null
+++ b/patches/wownero/0025-depends-remove-icu4c-monero-project-monero-8880.patch
@@ -0,0 +1,25 @@
+From e437fea14ce67d7d0073ad484a66cfc92daa3eb5 Mon Sep 17 00:00:00 2001
+From: Czarek Nakamoto <cyjan@mrcyjanek.net>
+Date: Tue, 3 Mar 2026 13:55:59 +0100
+Subject: [PATCH] depends: remove icu4c monero-project/monero#8880
+
+---
+ CMakeLists.txt | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index a4c163503..b744261ea 100644
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -1115,7 +1115,7 @@ if(MINGW)
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wa,-mbig-obj")
+ set(EXTRA_LIBRARIES mswsock;ws2_32;iphlpapi;crypt32;bcrypt)
+ if(DEPENDS)
+- set(ICU_LIBRARIES icuio icui18n icuuc icudata icutu iconv)
++ set(ICU_LIBRARIES iconv)
+ else()
+ # This is an extremely ugly hack to get around Boost not being built with static ICU.
+ # We reported the issue, we are waiting for upstream to fix this issue: https://github.com/boostorg/boost/issues/1079#issue-3384962885
+--
+2.51.0
+